diff --git a/.ci/Jenkinsfile_flaky b/.ci/Jenkinsfile_flaky new file mode 100644 index 0000000000000..3f77243da2c1b --- /dev/null +++ b/.ci/Jenkinsfile_flaky @@ -0,0 +1,113 @@ +#!/bin/groovy + +library 'kibana-pipeline-library' +kibanaLibrary.load() + +// Looks like 'oss:ciGroup:1' or 'oss:firefoxSmoke' +def JOB_PARTS = params.CI_GROUP.split(':') +def IS_XPACK = JOB_PARTS[0] == 'xpack' +def JOB = JOB_PARTS[1] +def CI_GROUP = JOB_PARTS.size() > 2 ? JOB_PARTS[2] : '' + +def worker = getWorkerFromParams(IS_XPACK, JOB, CI_GROUP) + +def workerFailures = [] + +currentBuild.displayName += trunc(" ${params.GITHUB_OWNER}:${params.branch_specifier}", 24) +currentBuild.description = "${params.CI_GROUP}
Executions: ${params.NUMBER_EXECUTIONS}" + +// Note: If you increase agent count, it will execute NUMBER_EXECUTIONS per agent. It will not divide them up amongst the agents +// e.g. NUMBER_EXECUTIONS = 25, agentCount = 4 results in 100 total executions +def agentCount = 1 + +stage("Kibana Pipeline") { + timeout(time: 180, unit: 'MINUTES') { + timestamps { + ansiColor('xterm') { + def agents = [:] + for(def agentNumber = 1; agentNumber <= agentCount; agentNumber++) { + agents["agent-${agentNumber}"] = { + catchError { + kibanaPipeline.withWorkers('flaky-test-runner', { + if (!IS_XPACK) { + kibanaPipeline.buildOss() + if (CI_GROUP == '1') { + runbld "./test/scripts/jenkins_build_kbn_tp_sample_panel_action.sh" + } + } else { + kibanaPipeline.buildXpack() + } + }, getWorkerMap(agentNumber, params.NUMBER_EXECUTIONS.toInteger(), worker, workerFailures))() + } + } + } + + parallel(agents) + + currentBuild.description += ", Failures: ${workerFailures.size()}" + + if (workerFailures.size() > 0) { + print "There were ${workerFailures.size()} test suite failures." + print "The executions that failed were:" + print workerFailures.join("\n") + print "Please check 'Test Result' and 'Pipeline Steps' pages for more info" + } + } + } + } +} + +def getWorkerFromParams(isXpack, job, ciGroup) { + if (!isXpack) { + if (job == 'firefoxSmoke') { + return kibanaPipeline.getPostBuildWorker('firefoxSmoke', { runbld './test/scripts/jenkins_firefox_smoke.sh' }) + } else if(job == 'visualRegression') { + return kibanaPipeline.getPostBuildWorker('visualRegression', { runbld './test/scripts/jenkins_visual_regression.sh' }) + } else { + return kibanaPipeline.getOssCiGroupWorker(ciGroup) + } + } + + if (job == 'firefoxSmoke') { + return kibanaPipeline.getPostBuildWorker('xpack-firefoxSmoke', { runbld './test/scripts/jenkins_xpack_firefox_smoke.sh' }) + } else if(job == 'visualRegression') { + return kibanaPipeline.getPostBuildWorker('xpack-visualRegression', { runbld './test/scripts/jenkins_xpack_visual_regression.sh' }) + } else { + return kibanaPipeline.getXpackCiGroupWorker(ciGroup) + } +} + +def getWorkerMap(agentNumber, numberOfExecutions, worker, workerFailures, maxWorkers = 14) { + def workerMap = [:] + def numberOfWorkers = Math.min(numberOfExecutions, maxWorkers) + + for(def i = 1; i <= numberOfWorkers; i++) { + def workerExecutions = numberOfExecutions/numberOfWorkers + (i <= numberOfExecutions%numberOfWorkers ? 1 : 0) + + workerMap["agent-${agentNumber}-worker-${i}"] = { workerNumber -> + for(def j = 0; j < workerExecutions; j++) { + print "Execute agent-${agentNumber} worker-${workerNumber}: ${j}" + withEnv(["JOB=agent-${agentNumber}-worker-${workerNumber}-${j}"]) { + catchError { + try { + worker(workerNumber) + } catch (ex) { + workerFailures << "agent-${agentNumber} worker-${workerNumber}-${j}" + throw ex + } + } + } + } + } + } + + return workerMap +} + +def trunc(str, length) { + if (str.size() >= length) { + return str.take(length) + "..." + } + + return str; +} diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4ae7b27a58ee4..7001e32754abb 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -10,6 +10,7 @@ /src/plugins/data/ @elastic/kibana-app-arch /src/plugins/kibana_utils/ @elastic/kibana-app-arch /src/plugins/kibana_react/ @elastic/kibana-app-arch +/src/plugins/navigation/ @elastic/kibana-app-arch # APM /x-pack/legacy/plugins/apm/ @elastic/apm-ui diff --git a/.github/labeler.yml b/.github/labeler.yml index 8a776a909cd9e..57b299912ae9e 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -2,6 +2,7 @@ - src/plugins/data/**/* - src/plugins/embeddable/**/* - src/plugins/kibana_react/**/* +- src/plugins/navigation/**/* - src/plugins/kibana_utils/**/* - src/legacy/core_plugins/dashboard_embeddable_container/**/* - src/legacy/core_plugins/data/**/* diff --git a/.i18nrc.json b/.i18nrc.json index 0ea51fe8212c4..d77293e3dc8f7 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -4,6 +4,7 @@ "data": ["src/legacy/core_plugins/data", "src/plugins/data"], "expressions": "src/legacy/core_plugins/expressions", "kibana_react": "src/legacy/core_plugins/kibana_react", + "navigation": "src/legacy/core_plugins/navigation", "server": "src/legacy/server", "console": "src/legacy/core_plugins/console", "core": "src/core", diff --git a/Jenkinsfile b/Jenkinsfile index fca814b265295..4820de9876737 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,6 +1,7 @@ #!/bin/groovy library 'kibana-pipeline-library' +kibanaLibrary.load() stage("Kibana Pipeline") { // This stage is just here to help the BlueOcean UI a little bit timeout(time: 180, unit: 'MINUTES') { @@ -8,301 +9,42 @@ stage("Kibana Pipeline") { // This stage is just here to help the BlueOcean UI a ansiColor('xterm') { catchError { parallel([ - 'kibana-intake-agent': legacyJobRunner('kibana-intake'), - 'x-pack-intake-agent': legacyJobRunner('x-pack-intake'), - 'kibana-oss-agent': withWorkers('kibana-oss-tests', { buildOss() }, [ - 'oss-ciGroup1': getOssCiGroupWorker(1), - 'oss-ciGroup2': getOssCiGroupWorker(2), - 'oss-ciGroup3': getOssCiGroupWorker(3), - 'oss-ciGroup4': getOssCiGroupWorker(4), - 'oss-ciGroup5': getOssCiGroupWorker(5), - 'oss-ciGroup6': getOssCiGroupWorker(6), - 'oss-ciGroup7': getOssCiGroupWorker(7), - 'oss-ciGroup8': getOssCiGroupWorker(8), - 'oss-ciGroup9': getOssCiGroupWorker(9), - 'oss-ciGroup10': getOssCiGroupWorker(10), - 'oss-ciGroup11': getOssCiGroupWorker(11), - 'oss-ciGroup12': getOssCiGroupWorker(12), - 'oss-firefoxSmoke': getPostBuildWorker('firefoxSmoke', { runbld './test/scripts/jenkins_firefox_smoke.sh' }), - // 'oss-visualRegression': getPostBuildWorker('visualRegression', { runbld './test/scripts/jenkins_visual_regression.sh' }), + 'kibana-intake-agent': kibanaPipeline.legacyJobRunner('kibana-intake'), + 'x-pack-intake-agent': kibanaPipeline.legacyJobRunner('x-pack-intake'), + 'kibana-oss-agent': kibanaPipeline.withWorkers('kibana-oss-tests', { kibanaPipeline.buildOss() }, [ + 'oss-ciGroup1': kibanaPipeline.getOssCiGroupWorker(1), + 'oss-ciGroup2': kibanaPipeline.getOssCiGroupWorker(2), + 'oss-ciGroup3': kibanaPipeline.getOssCiGroupWorker(3), + 'oss-ciGroup4': kibanaPipeline.getOssCiGroupWorker(4), + 'oss-ciGroup5': kibanaPipeline.getOssCiGroupWorker(5), + 'oss-ciGroup6': kibanaPipeline.getOssCiGroupWorker(6), + 'oss-ciGroup7': kibanaPipeline.getOssCiGroupWorker(7), + 'oss-ciGroup8': kibanaPipeline.getOssCiGroupWorker(8), + 'oss-ciGroup9': kibanaPipeline.getOssCiGroupWorker(9), + 'oss-ciGroup10': kibanaPipeline.getOssCiGroupWorker(10), + 'oss-ciGroup11': kibanaPipeline.getOssCiGroupWorker(11), + 'oss-ciGroup12': kibanaPipeline.getOssCiGroupWorker(12), + 'oss-firefoxSmoke': kibanaPipeline.getPostBuildWorker('firefoxSmoke', { runbld './test/scripts/jenkins_firefox_smoke.sh' }), + // 'oss-visualRegression': kibanaPipeline.getPostBuildWorker('visualRegression', { runbld './test/scripts/jenkins_visual_regression.sh' }), ]), - 'kibana-xpack-agent': withWorkers('kibana-xpack-tests', { buildXpack() }, [ - 'xpack-ciGroup1': getXpackCiGroupWorker(1), - 'xpack-ciGroup2': getXpackCiGroupWorker(2), - 'xpack-ciGroup3': getXpackCiGroupWorker(3), - 'xpack-ciGroup4': getXpackCiGroupWorker(4), - 'xpack-ciGroup5': getXpackCiGroupWorker(5), - 'xpack-ciGroup6': getXpackCiGroupWorker(6), - 'xpack-ciGroup7': getXpackCiGroupWorker(7), - 'xpack-ciGroup8': getXpackCiGroupWorker(8), - 'xpack-ciGroup9': getXpackCiGroupWorker(9), - 'xpack-ciGroup10': getXpackCiGroupWorker(10), - 'xpack-firefoxSmoke': getPostBuildWorker('xpack-firefoxSmoke', { runbld './test/scripts/jenkins_xpack_firefox_smoke.sh' }), - // 'xpack-visualRegression': getPostBuildWorker('xpack-visualRegression', { runbld './test/scripts/jenkins_xpack_visual_regression.sh' }), + 'kibana-xpack-agent': kibanaPipeline.withWorkers('kibana-xpack-tests', { kibanaPipeline.buildXpack() }, [ + 'xpack-ciGroup1': kibanaPipeline.getXpackCiGroupWorker(1), + 'xpack-ciGroup2': kibanaPipeline.getXpackCiGroupWorker(2), + 'xpack-ciGroup3': kibanaPipeline.getXpackCiGroupWorker(3), + 'xpack-ciGroup4': kibanaPipeline.getXpackCiGroupWorker(4), + 'xpack-ciGroup5': kibanaPipeline.getXpackCiGroupWorker(5), + 'xpack-ciGroup6': kibanaPipeline.getXpackCiGroupWorker(6), + 'xpack-ciGroup7': kibanaPipeline.getXpackCiGroupWorker(7), + 'xpack-ciGroup8': kibanaPipeline.getXpackCiGroupWorker(8), + 'xpack-ciGroup9': kibanaPipeline.getXpackCiGroupWorker(9), + 'xpack-ciGroup10': kibanaPipeline.getXpackCiGroupWorker(10), + 'xpack-firefoxSmoke': kibanaPipeline.getPostBuildWorker('xpack-firefoxSmoke', { runbld './test/scripts/jenkins_xpack_firefox_smoke.sh' }), + // 'xpack-visualRegression': kibanaPipeline.getPostBuildWorker('xpack-visualRegression', { runbld './test/scripts/jenkins_xpack_visual_regression.sh' }), ]), ]) } - node('flyweight') { - // If the build doesn't have a result set by this point, there haven't been any errors and it can be marked as a success - // The e-mail plugin for the infra e-mail depends upon this being set - currentBuild.result = currentBuild.result ?: 'SUCCESS' - - sendMail() - } + kibanaPipeline.sendMail() } } } } - -def withWorkers(name, preWorkerClosure = {}, workerClosures = [:]) { - return { - jobRunner('tests-xl', true) { - try { - doSetup() - preWorkerClosure() - - def nextWorker = 1 - def worker = { workerClosure -> - def workerNumber = nextWorker - nextWorker++ - - return { - workerClosure(workerNumber) - } - } - - def workers = [:] - workerClosures.each { workerName, workerClosure -> - workers[workerName] = worker(workerClosure) - } - - parallel(workers) - } finally { - catchError { - uploadAllGcsArtifacts(name) - } - - catchError { - runbldJunit() - } - - catchError { - publishJunit() - } - - catchError { - runErrorReporter() - } - } - } - } -} - -def getPostBuildWorker(name, closure) { - return { workerNumber -> - def kibanaPort = "61${workerNumber}1" - def esPort = "61${workerNumber}2" - def esTransportPort = "61${workerNumber}3" - - withEnv([ - "CI_WORKER_NUMBER=${workerNumber}", - "TEST_KIBANA_HOST=localhost", - "TEST_KIBANA_PORT=${kibanaPort}", - "TEST_KIBANA_URL=http://elastic:changeme@localhost:${kibanaPort}", - "TEST_ES_URL=http://elastic:changeme@localhost:${esPort}", - "TEST_ES_TRANSPORT_PORT=${esTransportPort}", - "IS_PIPELINE_JOB=1", - ]) { - closure() - } - } -} - -def getOssCiGroupWorker(ciGroup) { - return getPostBuildWorker("ciGroup" + ciGroup, { - withEnv([ - "CI_GROUP=${ciGroup}", - "JOB=kibana-ciGroup${ciGroup}", - ]) { - runbld "./test/scripts/jenkins_ci_group.sh" - } - }) -} - -def getXpackCiGroupWorker(ciGroup) { - return getPostBuildWorker("xpack-ciGroup" + ciGroup, { - withEnv([ - "CI_GROUP=${ciGroup}", - "JOB=xpack-kibana-ciGroup${ciGroup}", - ]) { - runbld "./test/scripts/jenkins_xpack_ci_group.sh" - } - }) -} - -def legacyJobRunner(name) { - return { - parallel([ - "${name}": { - withEnv([ - "JOB=${name}", - ]) { - jobRunner('linux && immutable', false) { - try { - runbld('.ci/run.sh', true) - } finally { - catchError { - uploadAllGcsArtifacts(name) - } - catchError { - publishJunit() - } - catchError { - runErrorReporter() - } - } - } - } - } - ]) - } -} - -def jobRunner(label, useRamDisk, closure) { - node(label) { - if (useRamDisk) { - // Move to a temporary workspace, so that we can symlink the real workspace into /dev/shm - def originalWorkspace = env.WORKSPACE - ws('/tmp/workspace') { - sh """ - mkdir -p /dev/shm/workspace - mkdir -p '${originalWorkspace}' # create all of the directories leading up to the workspace, if they don't exist - rm --preserve-root -rf '${originalWorkspace}' # then remove just the workspace, just in case there's stuff in it - ln -s /dev/shm/workspace '${originalWorkspace}' - """ - } - } - - def scmVars = checkout scm - - withEnv([ - "CI=true", - "HOME=${env.JENKINS_HOME}", - "PR_SOURCE_BRANCH=${env.ghprbSourceBranch ?: ''}", - "PR_TARGET_BRANCH=${env.ghprbTargetBranch ?: ''}", - "PR_AUTHOR=${env.ghprbPullAuthorLogin ?: ''}", - "TEST_BROWSER_HEADLESS=1", - "GIT_BRANCH=${scmVars.GIT_BRANCH}", - ]) { - withCredentials([ - string(credentialsId: 'vault-addr', variable: 'VAULT_ADDR'), - string(credentialsId: 'vault-role-id', variable: 'VAULT_ROLE_ID'), - string(credentialsId: 'vault-secret-id', variable: 'VAULT_SECRET_ID'), - ]) { - // scm is configured to check out to the ./kibana directory - dir('kibana') { - closure() - } - } - } - } -} - -// TODO what should happen if GCS, Junit, or email publishing fails? Unstable build? Failed build? - -def uploadGcsArtifact(workerName, pattern) { - def storageLocation = "gs://kibana-ci-artifacts/jobs/${env.JOB_NAME}/${BUILD_NUMBER}/${workerName}" // TODO - // def storageLocation = "gs://kibana-pipeline-testing/jobs/pipeline-test/${BUILD_NUMBER}/${workerName}" - - googleStorageUpload( - credentialsId: 'kibana-ci-gcs-plugin', - bucket: storageLocation, - pattern: pattern, - sharedPublicly: true, - showInline: true, - ) -} - -def uploadAllGcsArtifacts(workerName) { - def ARTIFACT_PATTERNS = [ - 'target/kibana-*', - 'target/junit/**/*', - 'test/**/screenshots/**/*.png', - 'test/functional/failure_debug/html/*.html', - 'x-pack/test/**/screenshots/**/*.png', - 'x-pack/test/functional/failure_debug/html/*.html', - 'x-pack/test/functional/apps/reporting/reports/session/*.pdf', - ] - - ARTIFACT_PATTERNS.each { pattern -> - uploadGcsArtifact(workerName, pattern) - } -} - -def publishJunit() { - junit(testResults: 'target/junit/**/*.xml', allowEmptyResults: true, keepLongStdio: true) -} - -def sendMail() { - sendInfraMail() - sendKibanaMail() -} - -def sendInfraMail() { - catchError { - step([ - $class: 'Mailer', - notifyEveryUnstableBuild: true, - recipients: 'infra-root+build@elastic.co', - sendToIndividuals: false - ]) - } -} - -def sendKibanaMail() { - catchError { - def buildStatus = buildUtils.getBuildStatus() - - if(params.NOTIFY_ON_FAILURE && buildStatus != 'SUCCESS' && buildStatus != 'ABORTED') { - emailext( - to: 'build-kibana@elastic.co', - subject: "${env.JOB_NAME} - Build # ${env.BUILD_NUMBER} - ${buildStatus}", - body: '${SCRIPT,template="groovy-html.template"}', - mimeType: 'text/html', - ) - } - } -} - -def runbld(script, enableJunitProcessing = false) { - def extraConfig = enableJunitProcessing ? "" : "--config ${env.WORKSPACE}/kibana/.ci/runbld_no_junit.yml" - - sh "/usr/local/bin/runbld -d '${pwd()}' ${extraConfig} ${script}" -} - -def runbldJunit() { - sh "/usr/local/bin/runbld -d '${pwd()}' ${env.WORKSPACE}/kibana/test/scripts/jenkins_runbld_junit.sh" -} - -def bash(script) { - sh "#!/bin/bash\n${script}" -} - -def doSetup() { - runbld "./test/scripts/jenkins_setup.sh" -} - -def buildOss() { - runbld "./test/scripts/jenkins_build_kibana.sh" -} - -def buildXpack() { - runbld "./test/scripts/jenkins_xpack_build_kibana.sh" -} - -def runErrorReporter() { - bash """ - source src/dev/ci_setup/setup_env.sh - node scripts/report_failed_tests - """ -} diff --git a/config/kibana.yml b/config/kibana.yml index 9525a6423d90a..47482f9e6d59f 100644 --- a/config/kibana.yml +++ b/config/kibana.yml @@ -50,7 +50,8 @@ #server.ssl.key: /path/to/your/server.key # Optional settings that provide the paths to the PEM-format SSL certificate and key files. -# These files validate that your Elasticsearch backend uses the same key files. +# These files are used to verify the identity of Kibana to Elasticsearch and are required when +# xpack.ssl.verification_mode in Elasticsearch is set to either certificate or full. #elasticsearch.ssl.certificate: /path/to/your/client.crt #elasticsearch.ssl.key: /path/to/your/client.key diff --git a/docs/canvas/canvas-function-reference.asciidoc b/docs/canvas/canvas-function-reference.asciidoc index df4b17e905772..07f3cf028dc0e 100644 --- a/docs/canvas/canvas-function-reference.asciidoc +++ b/docs/canvas/canvas-function-reference.asciidoc @@ -25,6 +25,32 @@ A † denotes an argument can be passed multiple times. Returns `true` if all of the conditions are met. See also <>. +*Expression syntax* +[source,js] +---- +all {neq “foo”} {neq “bar”} {neq “fizz”} +all condition={gt 10} condition={lt 20} +---- + +*Code example* +[source,text] +---- +filters +| demodata +| math "mean(percent_uptime)" +| formatnumber "0.0%" +| metric "Average uptime" + metricFont={ + font size=48 family="'Open Sans', Helvetica, Arial, sans-serif" + color={ + if {all {gte 0} {lt 0.8}} then="red" else="green" + } + align="center" lHeight=48 + } +| render +---- +This sets the color of the metric text to `”red”` if the context passed into `metric` is greater than or equal to 0 and less than 0.8. Otherwise, the color is set to `"green"`. + *Accepts:* `null` [cols="3*^<"] @@ -47,6 +73,24 @@ Alias: `condition` Converts between core types, including `string`, `number`, `null`, `boolean`, and `date`, and renames columns. See also <> and <>. +*Expression syntax* +[source,js] +---- +alterColumn “cost” type=”string” +alterColumn column=”@timestamp” name=”foo” +---- + +*Code example* +[source,text] +---- +filters +| demodata +| alterColumn “time” name=”time_in_ms” type=”number” +| table +| render +---- +This renames the `time` column to `time_in_ms` and converts the type of the column’s values from `date` to `number`. + *Accepts:* `datatable` [cols="3*^<"] @@ -77,6 +121,27 @@ Alias: `column` Returns `true` if at least one of the conditions is met. See also <>. +*Expression syntax* +[source,js] +---- +any {eq “foo”} {eq “bar”} {eq “fizz”} +any condition={lte 10} condition={gt 30} +---- + +*Code example* +[source,text] +---- +filters +| demodata +| filterrows { + getCell "project" | any {eq "elasticsearch"} {eq "kibana"} {eq "x-pack"} + } +| pointseries color="project" size="max(price)" +| pie +| render +---- +This filters out any rows that don’t contain `“elasticsearch”`, `“kibana”` or `“x-pack”` in the `project` field. + *Accepts:* `null` [cols="3*^<"] @@ -99,6 +164,28 @@ Alias: `condition` Creates a `datatable` with a single value. See also <>. +*Expression syntax* +[source,js] +---- +as +as “foo” +as name=”bar” +---- + +*Code example* +[source,text] +---- +filters +| demodata +| ply by="project" fn={math "count(username)" | as "num_users"} fn={math "mean(price)" | as "price"} +| pointseries x="project" y="num_users" size="price" color="project" +| plot +| render +---- +`as` casts any primitive value (`string`, `number`, `date`, `null`) into a `datatable` with a single row and a single column with the given name (or defaults to `"value"` if no name is provided). This is useful when piping a primitive value into a function that only takes `datatable` as an input. + +In the example above, `ply` expects each `fn` subexpression to return a `datatable` in order to merge the results of each `fn` back into a `datatable`, but using a `math` aggregation in the subexpressions returns a single `math` value, which is then cast into a `datatable` using `as`. + *Accepts:* `string`, `boolean`, `number`, `null` [cols="3*^<"] @@ -123,6 +210,21 @@ Default: `"value"` Retrieves Canvas workpad asset objects to provide as argument values. Usually images. +*Expression syntax* +[source,js] +---- +asset "asset-52f14f2b-fee6-4072-92e8-cd2642665d02" +asset id="asset-498f7429-4d56-42a2-a7e4-8bf08d98d114" +---- + +*Code example* +[source,text] +---- +image dataurl={asset "asset-c661a7cc-11be-45a1-a401-d7592ea7917a"} mode="contain" +| render +---- +The image asset stored with the ID `“asset-c661a7cc-11be-45a1-a401-d7592ea7917a”` is passed into the `dataurl` argument of the `image` function to display the stored asset. + *Accepts:* `null` [cols="3*^<"] @@ -145,6 +247,27 @@ Alias: `id` Configures the axis of a visualization. Only used with <>. +*Expression syntax* +[source,js] +---- +axisConfig show=false +axisConfig position=”right” min=0 max=10 tickSize=1 +---- + +*Code example* +[source,text] +---- +filters +| demodata +| pointseries x="size(cost)" y="project" color="project" +| plot defaultStyle={seriesStyle bars=0.75 horizontalBars=true} + legend=false + xaxis={axisConfig position="top" min=0 max=400 tickSize=100} + yaxis={axisConfig position="right"} +| render +---- +This sets the `x-axis` to display on the top of the chart and sets the range of values to `0-400` with ticks displayed at `100` intervals. The `y-axis` is configured to display on the `right`. + *Accepts:* `null` [cols="3*^<"] @@ -188,6 +311,34 @@ Default: `true` Builds a `case` (including a condition/result) to pass to the <> function. +*Expression syntax* +[source,js] +---- +case 0 then=”red” +case when=5 then=”yellow” +case if={lte 50} then=”green” +---- + +*Code example* +[source,text] +---- +math "random()" +| progress shape="gauge" label={formatnumber "0%"} + font={font size=24 family="'Open Sans', Helvetica, Arial, sans-serif" align="center" + color={ + switch {case if={lte 0.5} then="green"} + {case if={all {gt 0.5} {lte 0.75}} then="orange"} + default="red" + }} + valueColor={ + switch {case if={lte 0.5} then="green"} + {case if={all {gt 0.5} {lte 0.75}} then="orange"} + default="red" + } +| render +---- +This sets the color of the progress indicator and the color of the label to `"green"` if the value is less than or equal to `0.5`, `"orange"` if the value is greater than `0.5` and less than or equal to `0.75`, and `"red"` if `none` of the case conditions are met. + *Accepts:* `any` [cols="3*^<"] @@ -229,6 +380,24 @@ Clears the _context_, and returns `null`. Includes or excludes columns from a data table. If you specify both, this will exclude first. +*Expression syntax* +[source,js] +---- +columns include=”@timestamp, projects, cost” +columns exclude=”username, country, age” +---- + +*Code example* +[source,text] +---- +filters +| demodata +| columns include="price, cost, state, project" +| table +| render +---- +This only keeps the `price`, `cost`, `state`, and `project` columns from the `demodata` data source and removes all other columns. + *Accepts:* `datatable` [cols="3*^<"] @@ -257,6 +426,31 @@ Compares the _context_ to specified value to determine `true` or `false`. Usually used in combination with <> or <>. This only works with primitive types, such as `number`, `string`, and `boolean`. See also <>, <>, <>, <>, <>, and <>. +*Expression syntax* +[source,js] +---- +compare “neq” to=”elasticsearch” +compare op=”lte” to=100 +---- + +*Code example* +[source,text] +---- +filters +| demodata +| mapColumn project + fn=${getCell project | + switch + {case if={compare eq to=kibana} then=kibana} + {case if={compare eq to=elasticsearch} then=elasticsearch} + default="other" + } +| pointseries size="size(cost)" color="project" +| pie +| render +---- +This maps all `project` values that aren’t `“kibana”` and `“elasticsearch”` to `“other”`. Alternatively, you can use the individual comparator functions instead of compare. See <>, <>, <>, <>, <>, and <>. + *Accepts:* `string`, `number`, `boolean`, `null` [cols="3*^<"] @@ -287,6 +481,35 @@ Alias: `this`, `b` Creates an object used for styling an element's container, including background, border, and opacity. +*Expression syntax* +[source,js] +---- +containerStyle backgroundColor=”red”’ +containerStyle borderRadius=”50px” +containerStyle border=”1px solid black” +containerStyle padding=”5px” +containerStyle opacity=”0.5” +containerStyle overflow=”hidden” +containerStyle backgroundImage={asset id=asset-f40d2292-cf9e-4f2c-8c6f-a504a25e949c} + backgroundRepeat="no-repeat" + backgroundSize="cover" +---- + +*Code example* +[source,text] +---- +shape "star" fill="#E61D35" maintainAspect=true +| render + containerStyle={ + containerStyle backgroundColor="#F8D546" + borderRadius="200px" + border="4px solid #05509F" + padding="0px" + opacity="0.9" + overflow="hidden" + } +---- + *Accepts:* `null` [cols="3*^<"] @@ -346,6 +569,22 @@ Default: `"hidden"` Returns whatever you pass into it. This can be useful when you need to use the _context_ as an argument to a function as a sub-expression. +*Expression syntax* +[source,js] +---- +context +---- + +*Code example* +[source,text] +---- +date +| formatdate "LLLL" +| markdown "Last updated: " {context} +| render +---- +Using the `context` function allows us to pass the output, or _context_, of the previous function as a value to an argument in the next function. Here we get the formatted date string from the previous function and pass it as `content` for the markdown element. + *Accepts:* `any` *Returns:* Original _context_ @@ -356,6 +595,26 @@ _context_ as an argument to a function as a sub-expression. Creates a `datatable` from CSV input. +*Expression syntax* +[source,js] +---- +csv “fruit, stock + kiwi, 10 + Banana, 5” +---- + +*Code example* +[source,text] +---- +csv "fruit,stock + kiwi,10 + banana,5" +| pointseries color=fruit size=stock +| pie +| render +---- +This is useful for quickly mocking data. + *Accepts:* `null` [cols="3*^<"] @@ -390,6 +649,30 @@ Alias: `data` Returns the current time, or a time parsed from a `string`, as milliseconds since epoch. +*Expression syntax* +[source,js] +---- +date +date value=1558735195 +date “2019-05-24T21:59:55+0000” +date “01/31/2019” format=”MM/DD/YYYY” +---- + +*Code example* +[source,text] +---- +date +| formatdate "LLL" +| markdown {context} + font={font family="Arial, sans-serif" size=30 align="left" + color="#000000" + weight="normal" + underline=false + italic=false} +| render +---- +Using `date` without passing any arguments will return the current date and time. + *Accepts:* `null` [cols="3*^<"] @@ -418,6 +701,24 @@ using the `format` argument. Must be an ISO8601 string or you must provide the f A mock data set that includes project CI times with usernames, countries, and run phases. +*Expression syntax* +[source,js] +---- +demodata +demodata “ci” +demodata type=”shirts” +---- + +*Code example* +[source,text] +---- +filters +| demodata +| table +| render +---- +`demodata` is a mock data set that you can use to start playing around in Canvas. + *Accepts:* `filter` [cols="3*^<"] @@ -442,6 +743,23 @@ Default: `"ci"` Executes multiple sub-expressions, then returns the original _context_. Use for running functions that produce an action or side effect without changing the original _context_. +*Expression syntax* +[source,js] +---- +do fn={something cool} +---- + +*Code example* +[source,text] +---- +filters +| demodata +| do fn={something cool} +| table +| render +---- +`do` should be used to invoke a function that produces as a side effect without changing the `context`. + *Accepts:* `any` [cols="3*^<"] @@ -464,6 +782,22 @@ Aliases: `expression`, `exp`, `fn`, `function` Configures a dropdown filter control element. +*Expression syntax* +[source,js] +---- +dropdownControl valueColumn=project filterColumn=project +dropdownControl valueColumn=agent filterColumn=agent.keyword filterGroup=group1 +---- + +*Code example* +[source,text] +---- +demodata +| dropdownControl valueColumn=project filterColumn=project +| render +---- +This creates a dropdown filter element. It requires a data source and uses the unique values from the given `valueColumn` (i.e. `project`) and applies the filter to the `project` column. Note: `filterColumn` should point to a keyword type field for Elasticsearch data sources. + *Accepts:* `datatable` [cols="3*^<"] @@ -496,6 +830,33 @@ Configures a dropdown filter control element. Returns whether the _context_ is equal to the argument. +*Expression syntax* +[source,js] +---- +eq true +eq null +eq 10 +eq “foo” +---- + +*Code example* +[source,text] +---- +filters +| demodata +| mapColumn project + fn=${getCell project | + switch + {case if={eq kibana} then=kibana} + {case if={eq elasticsearch} then=elasticsearch} + default="other" + } +| pointseries size="size(cost)" color="project" +| pie +| render +---- +This changes all values in the project column that don’t equal `“kibana”` or `“elasticsearch”` to `“other”`. + *Accepts:* `boolean`, `number`, `string`, `null` [cols="3*^<"] @@ -518,6 +879,28 @@ Alias: `value` Queries {es} for the number of hits matching the specified query. +*Expression syntax* +[source,js] +---- +escount index=”logstash-*” +escount "currency:\"EUR\"" index=”kibana_sample_data_ecommerce” +escount query="response:404" index=”kibana_sample_data_logs” +---- + +*Code example* +[source,text] +---- +filters +| escount "Cancelled:true" index="kibana_sample_data_flights" +| math "value" +| progress shape="semicircle" + label={formatnumber 0,0} + font={font size=24 family="'Open Sans', Helvetica, Arial, sans-serif" color="#000000" align=center} + max={filters | escount index="kibana_sample_data_flights"} +| render +---- +The first `escount` expression retrieves the number of flights that were cancelled. The second `escount` expression retrieves the total number of flights. + *Accepts:* `filter` [cols="3*^<"] @@ -549,6 +932,34 @@ Default: `_all` Queries {es} for raw documents. Specify the fields you want to retrieve, especially if you are asking for a lot of rows. +*Expression syntax* +[source,js] +---- +esdocs index=”logstash-*” +esdocs "currency:\"EUR\"" index=”kibana_sample_data_ecommerce” +esdocs query="response:404" index=”kibana_sample_data_logs” +esdocs index=”kibana_sample_data_flights” count=100 +esdocs index="kibana_sample_data_flights" sort="AvgTicketPrice, asc" +---- + +*Code example* +[source,text] +---- +filters +| esdocs index="kibana_sample_data_ecommerce" + fields="customer_gender, taxful_total_price, order_date" + sort="order_date, asc" + count=10000 +| mapColumn "order_date" + fn={getCell "order_date" | date {context} | rounddate "YYYY-MM-DD"} +| alterColumn "order_date" type="date" +| pointseries x="order_date" y="sum(taxful_total_price)" color="customer_gender" +| plot defaultStyle={seriesStyle lines=3} + palette={palette "#7ECAE3" "#003A4D" gradient=true} +| render +---- +This retrieves the latest 10000 documents data from the `kibana_sample_data_ecommerce` index sorted by `order_date` in ascending order and only requests the `customer_gender`, `taxful_total_price`, and `order_date` fields. + *Accepts:* `filter` [cols="3*^<"] @@ -593,6 +1004,21 @@ Default: `"_all"` Queries {es} using {es} SQL. +*Expression syntax* +[source,js] +---- +essql query=”SELECT * FROM \”logstash*\”” +essql “SELECT * FROM \”apm*\”” count=10000 +---- + +*Code example* +[source,text] +---- +filters +| essql query="SELECT Carrier, FlightDelayMin, AvgTicketPrice FROM \"kibana_sample_data_flights\"" +---- +This retrieves the `Carrier`, `FlightDelayMin`, and `AvgTicketPrice` fields from the “kibana_sample_data_flights” index. + *Accepts:* `filter` [cols="3*^<"] @@ -627,6 +1053,26 @@ Default: `UTC` Creates a filter that matches a given column to an exact value. +*Expression syntax* +[source,js] +---- +exactly “state” value=”running” +exactly “age” value=50 filterGroup=”group2” +exactly column=“project” value=”beats” +---- + +*Code example* +[source,text] +---- +filters +| exactly column=project value=elasticsearch +| demodata +| pointseries x=project y="mean(age)" +| plot defaultStyle={seriesStyle bars=1} +| render +---- +The `exactly` filter here is added to existing filters retrieved by the `filters` function and further filters down the data to only have `”elasticsearch”` data. The `exactly` filter only applies to this one specific element and will not affect other elements in the workpad. + *Accepts:* `filter` [cols="3*^<"] @@ -664,6 +1110,29 @@ capitalization. Filters rows in a `datatable` based on the return value of a sub-expression. +*Expression syntax* +[source,js] +---- +filterrows {getCell “project” | eq “kibana”} +filterrows fn={getCell “age” | gt 50} +---- + +*Code example* +[source,text] +---- +filters +| demodata +| filterrows {getCell "country" | any {eq "IN"} {eq "US"} {eq "CN"}} +| mapColumn "@timestamp" + fn={getCell "@timestamp" | rounddate "YYYY-MM"} +| alterColumn "@timestamp" type="date" +| pointseries x="@timestamp" y="mean(cost)" color="country" +| plot defaultStyle={seriesStyle points="2" lines="1"} + palette={palette "#01A4A4" "#CC6666" "#D0D102" "#616161" "#00A1CB" "#32742C" "#F18D05" "#113F8C" "#61AE24" "#D70060" gradient=false} +| render +---- +This uses `filterrows` to only keep data from India (`IN`), the United States (`US`), and China (`CN`). + *Accepts:* `datatable` [cols="3*^<"] @@ -688,6 +1157,34 @@ and a `false` value removes it. Aggregates element filters from the workpad for use elsewhere, usually a data source. +*Expression syntax* +[source,js] +---- +filters +filters group=”timefilter1” +filters group=”timefilter2” group=”dropdownfilter1” ungrouped=true +---- + +*Code example* +[source,text] +---- +filters group=group2 ungrouped=true +| demodata +| pointseries x="project" y="size(cost)" color="project" +| plot defaultStyle={seriesStyle bars=0.75} legend=false + font={ + font size=14 + family="'Open Sans', Helvetica, Arial, sans-serif" + align="left" + color="#FFFFFF" + weight="lighter" + underline=true + italic=true + } +| render +---- +`filters` sets the existing filters as context and accepts `group` parameter to create filter groups. + *Accepts:* `null` [cols="3*^<"] @@ -718,6 +1215,38 @@ Default: `false` Creates a font style. +*Expression syntax* +[source,js] +---- +font size=12 +font family=Arial +font align=middle +font color=pink +font weight=lighter +font underline=true +font italic=false +font lHeight=32 +---- + +*Code example* +[source,text] +---- +filters +| demodata +| pointseries x="project" y="size(cost)" color="project" +| plot defaultStyle={seriesStyle bars=0.75} legend=false + font={ + font size=14 + family="'Open Sans', Helvetica, Arial, sans-serif" + align="left" + color="#FFFFFF" + weight="lighter" + underline=true + italic=true + } +| render +---- + *Accepts:* `null` [cols="3*^<"] @@ -781,6 +1310,25 @@ Default: `"normal"` Formats an ISO8601 date string or a date in milliseconds since epoch using MomentJS. See https://momentjs.com/docs/#/displaying/. +*Expression syntax* +[source,js] +---- +formatdate format=”YYYY-MM-DD” +formatdate “MM/DD/YYYY” +---- + +*Code example* +[source,text] +---- +filters +| demodata +| mapColumn "time" fn={getCell time | formatdate "MMM 'YY"} +| pointseries x="time" y="sum(price)" color="state" +| plot defaultStyle={seriesStyle points=5} +| render +---- +This transforms the dates in the `time` field into strings that look like `“Jan ‘19”`, `“Feb ‘19”`, etc. using a MomentJS format. + *Accepts:* `number`, `string` [cols="3*^<"] @@ -803,6 +1351,26 @@ Alias: `format` Formats a `number` into a formatted `string` using NumeralJS. See http://numeraljs.com/#format. +*Expression syntax* +[source,js] +---- +formatnumber format=”$0,0.00” +formatnumber “0.0a” +---- + +*Code example* +[source,text] +---- +filters +| demodata +| math "mean(percent_uptime)" +| progress shape="gauge" + label={formatnumber "0%"} + font={font size=24 family="'Open Sans', Helvetica, Arial, sans-serif" color="#000000" align="center"} +| render +---- +The `formatnumber` subexpression receives the same `context` as the `progress` function, which is the output of the `math` function. It formats the value into a percentage. + *Accepts:* `number` [cols="3*^<"] diff --git a/docs/code/code-basic-nav.asciidoc b/docs/code/code-basic-nav.asciidoc deleted file mode 100644 index ccd290532b669..0000000000000 --- a/docs/code/code-basic-nav.asciidoc +++ /dev/null @@ -1,25 +0,0 @@ -[[code-basic-nav]] -== Basic navigation - -[float] -==== View file structure and information -The *File* tree on the left is the primary way to navigate through your folder structure. When you are in a directory, *Code* also presents a finder-like view on the right. Additionally, a file path breadcrumb makes it easy to go back to any parent directory. - -[float] -==== Git History and Blame -The *Directory* view shows the most recent commits. Clicking *View More* or *History* shows the complete commit history of the current folder or file. - -[role="screenshot"] -image::images/code-history.png[] - -Clicking *Blame* shows the most recent commit per line. - -[role="screenshot"] -image::images/code-blame.png[] - -[float] -==== Branch selector -You can use the Branch selector to view different branches of a repo. Note that code intelligence and search index are not available for any branch other than the master branch. - - -include::code-semantic-nav.asciidoc[] diff --git a/docs/code/code-import-first-repo.asciidoc b/docs/code/code-import-first-repo.asciidoc deleted file mode 100644 index 0c8b2660edcab..0000000000000 --- a/docs/code/code-import-first-repo.asciidoc +++ /dev/null @@ -1,48 +0,0 @@ -[[code-import-first-repo]] -== Import your first repo - -The easiest way to get started with *Code* is to import a real-world repository. - -[float] -==== Before you begin -You must have a {kib} instance up and running. - -If you are in an environment where you have multiple {kib} instances in a cluster, see the <>. - -[float] -==== Enable Code app -While in beta, you can turn on *Code* by adding the following line to `kibana.yaml`: - -[source,yaml] ----- -xpack.code.ui.enabled: true ----- - -[float] -==== Import your first repository -. In {Kib}, navigate to *Code*. - -. In the *Repository URL* field, paste the following GitHub clone URL: -+ -[source,bash] ----- -https://github.com/Microsoft/TypeScript-Node-Starter ----- - -`https` is recommend for cloning most git repositories. To clone a private repository, <>. - -. Click *Import*. -+ -A new item in the list displays the cloning and indexing progress of the `TypeScript-Node-Starter` repo. -+ -[role="screenshot"] -image::images/code-import-repo.png[] - -. After the indexing is complete, navigate to the repo by clicking its name in the list. -+ -[role="screenshot"] -image::images/code-starter-root.png[] -+ -Congratulations! You just imported your first repo into *Code*. - -include::code-repo-management.asciidoc[] diff --git a/docs/code/code-install-lang-server.asciidoc b/docs/code/code-install-lang-server.asciidoc deleted file mode 100644 index 4d4349e654c08..0000000000000 --- a/docs/code/code-install-lang-server.asciidoc +++ /dev/null @@ -1,25 +0,0 @@ -[[code-install-lang-server]] -== Install language server - -*Code* comes with built-in language support for TypeScript. You can install additional languages as a {kib} plugin. Plugin's reduce the distribution size to run Code inside {kib}. Install only the languages needed for your indexed repositories. - -[role="screenshot"] -image::images/code-lang-server-tab.png[] - -For the current version, *Code* supports the following languages in addition to TypeScript: - -* `Java` -* `GO` - -You can check the status of the language servers and get installation instructions on the *Language Servers* tab. Make sure the status of the language server is `INSTALLED` or `RUNNING` after you restart the {kib} instance. -[role="screenshot"] -image::images/code-lang-server-status.png[] - -//// -[float] -=== Ctag language server -*Code* also uses a Ctag language server to generate symbol information and code intelligence when a dedicated language server is not available. The code intelligence information generated by the Ctag language server is less accurate but covers more languages. -//// - - -include::code-basic-nav.asciidoc[] diff --git a/docs/code/code-multiple-kibana-instances-config.asciidoc b/docs/code/code-multiple-kibana-instances-config.asciidoc deleted file mode 100644 index 359e0764d28f1..0000000000000 --- a/docs/code/code-multiple-kibana-instances-config.asciidoc +++ /dev/null @@ -1,10 +0,0 @@ -[[code-multiple-kibana-instances-config]] -== Config for multiple {kib} instances -If you are using multiple instances of {kib}, you must manually assign at least one {kib} instance as a *Code* `node`. Add the following line of code to your `kibana.yml` file for each {kib} instance you wish to use and restart the instances: - -[source,yaml] ----- -xpack.code.codeNodeUrl: 'http://$YourCodeNodeAddress' ----- - -`$YourCodeNoteAddress` is the URL of your assigned *Code* node accessible by other {kib} instances. diff --git a/docs/code/code-repo-management.asciidoc b/docs/code/code-repo-management.asciidoc deleted file mode 100644 index 1fbd8934f57be..0000000000000 --- a/docs/code/code-repo-management.asciidoc +++ /dev/null @@ -1,76 +0,0 @@ -[[code-repo-management]] -== Repo management - -Code starts with an overview of your repositories. You can then use the UI to add, delete, and reindex a repo. -[role="screenshot"] -image::images/code-repo-management.png[] - -[float] -[[add-delete-a-repo]] -==== Add and delete a repo -The <> page provides step-by-step instructions for adding a GitHub repo to *Code*. You can fine-tune the hostname of the git clone URL in your `kibana.yml` file. - -For security reasons, Code allows only a few <>, such as github.com. To clone private repositories see <>. - -To delete a repository, go to the **Repositories** tab, find the name of the repo, and click *Delete*. - -[float] -[[clone-private-repo]] -==== Clone private repo with an SSH key -Clones of private repos require an SSH key for authentication. The username associated with your host must have write access to the repository you want to clone. - -The following section provides links for generating your ssh key through GitHub. If you already have an SSH key added through your host, skip to step 4. - -1. https://help.github.com/en/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent#generating-a-new-ssh-key[Generate an ssh key]. - -2. https://help.github.com/en/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent#adding-your-ssh-key-to-the-ssh-agent[Add the ssh key to your ssh-agent.] - -3. https://help.github.com/en/articles/adding-a-new-ssh-key-to-your-github-account[Add the ssh key to your host]. - -4. Copy the ssh key into `data/code/credentials/` under the {kib} folder. - -You can now copy private Git repositories into Code. - -To delete a repository, find the go to the **Repositories** tab, find the name of the repo and click *Delete*. - -[float] -[[reindex-a-repo]] -==== Reindex a repo -*Code* automatically reindexes an imported repo at set intervals, but in some cases, you might need to refresh the index manually. For example, you might refresh an index after a new language server installs. Or, you might want to update the index to the HEAD revision immediately. Click *Reindex* to initiate a reindex. - -[float] -[[clone-url-management]] -==== Clone URL management -For security reasons, *Code* only allows the following hostnames in the git clone URL by default: - -- `github.com` -- `gitlab.com` -- `bitbucket.org` -- `gitbox.apache.org` -- `eclipse.org` - -You can add your own hostname (for example, acme.com) to the whitelist by adding the following line to your `config/kibana.yaml` file: - -[source,yaml] ----- -xpack.code.security.gitHostWhitelist: [ "github.com", "gitlab.com", "bitbucket.org", "gitbox.apache.org", "eclipse.org", "acme.com" ] ----- - -Set `xpack.code.security.gitHostWhitelist` to [] (empty list) allow any hostname. - -You can also control the protocol to use for the clone address. By default, the following protocols are supported: `[ 'https', 'git', 'ssh' ]`. You can change this value by adding the following line to your `config/kibana.yaml` file. In this example, the user only wants to support the `https` protocol: - -[source,yaml] ----- -xpack.code.security.gitProtocolWhitelist: [ "https" ] ----- - -[float] -[[repo-limitations]] -==== Limitations -Consider the following limitations before cloning repositories: - -- Only Git is supported. Other version control systems, such as Mercurial and SVN, are not supported. -- Your disk might not have enough space to clone repositories due to {kib} watermark settings. To update your watermark settings, contact your system administrator and request additional disk space. - -include::code-install-lang-server.asciidoc[] diff --git a/docs/code/code-search.asciidoc b/docs/code/code-search.asciidoc deleted file mode 100644 index c3086bfb850a0..0000000000000 --- a/docs/code/code-search.asciidoc +++ /dev/null @@ -1,24 +0,0 @@ -[[code-search]] -== Search - -[float] -==== Typeahead search -The search bar is designed to help you find what you're looking for as quickly as possible. It shows `Symbols`, `Files`, and `Repos` results as you type. Clicking on any result takes you to the definition. You can use the search type dropdown to show all types of results or limit it to a specific search type. - -[role="screenshot"] -image::images/code-quick-search.png[] - -[float] -==== Full-text search -If the quick search results don’t contain what you are looking for, you can press ‘Enter’ to conduct a full-text search. -[role="screenshot"] -image::images/code-full-text-search.png[] -You can further refine the results by using the repo and language filters on the left. - -[float] -==== Search filter -You can also use the Search Filters to limit the search scope to certain repos before issuing a query. To search across all repos, remove any applied repo filters. By default, search results are limited to the repo you're currently viewing. -[role="screenshot"] -image::images/code-search-filter.png[] - -include::code-multiple-kibana-instances-config.asciidoc[] diff --git a/docs/code/code-semantic-nav.asciidoc b/docs/code/code-semantic-nav.asciidoc deleted file mode 100644 index a1535f1449dfe..0000000000000 --- a/docs/code/code-semantic-nav.asciidoc +++ /dev/null @@ -1,28 +0,0 @@ -[[code-semantic-nav]] - -== Semantic code navigation - -You can navigate a file with semantic code navigation features if: - -- *Code* supports the file's <> -- You have installed the corresponding <> - -[float] -==== Goto definition and find reference -Hovering your cursor over a symbol in a file opens information about the symbol, including its qualified name and documentation, when available. You can perform two actions: - -* *Goto Definition* navigates to the symbol definition. Definitions defined in a different repo can be found, provided that you have imported the repo with the definition. - -* *Find Reference* opens a panel that lists all references to the symbol. - -[role="screenshot"] -image::images/code-semantic-nav.png[] - -[float] -==== View symbol table -From the *Structure* tab, you can open a symbol table that details the structure of the current class. Clicking on a member function or variable jumps to its definition. - -[role="screenshot"] -image::images/code-symbol-table.png[] - -include::code-search.asciidoc[] diff --git a/docs/code/index.asciidoc b/docs/code/index.asciidoc deleted file mode 100644 index 799b356cb42c3..0000000000000 --- a/docs/code/index.asciidoc +++ /dev/null @@ -1,21 +0,0 @@ -[[code]] -= Code - -[partintro] --- - -beta[] - -Interaction with source code is pervasive and essential for any technology company. Speed of innovation is limited by how easy it is to search, navigate, and gain insight into your source code. -Elastic *Code* provides an easy-to-use code search solution that scales with your organization and empowers your team to understand your codebase faster than ever before. -*Code* offers the following functions: - -* Find references and definitions for any object or symbol -* Typeahead search for symbol definition, file, and repo -* Symbol table -* Full-text search with repo and language filters - -<> with *Code* by importing your first repo. --- - -include::code-import-first-repo.asciidoc[] diff --git a/docs/developer/core/development-modules.asciidoc b/docs/developer/core/development-modules.asciidoc index 603c0f2952d0c..b36be6bbb5d25 100644 --- a/docs/developer/core/development-modules.asciidoc +++ b/docs/developer/core/development-modules.asciidoc @@ -13,15 +13,6 @@ To prevent this from being an issue the ui module provides "autoloading" modules. The sole purpose of these modules is to extend the environment with certain components. Here is a breakdown of those modules: -- *`import 'ui/autoload/styles'`* - Imports all styles at the root of `src/legacy/ui/public/styles` - -- *`import 'ui/autoload/directives'`* - Imports all directives in `src/legacy/ui/public/directives` - -- *`import 'ui/autoload/filters'`* - Imports all filters in `src/legacy/ui/public/filters` - - *`import 'ui/autoload/modules'`* Imports angular and several ui services and "components" which Kibana depends on without importing. The full list of imports is hard coded in the diff --git a/docs/development/core/public/kibana-plugin-public.chromedoctitle.change.md b/docs/development/core/public/kibana-plugin-public.chromedoctitle.change.md new file mode 100644 index 0000000000000..eba149bf93a4c --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.chromedoctitle.change.md @@ -0,0 +1,34 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ChromeDocTitle](./kibana-plugin-public.chromedoctitle.md) > [change](./kibana-plugin-public.chromedoctitle.change.md) + +## ChromeDocTitle.change() method + +Changes the current document title. + +Signature: + +```typescript +change(newTitle: string | string[]): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| newTitle | string | string[] | | + +Returns: + +`void` + +## Example + +How to change the title of the document + +```ts +chrome.docTitle.change('My application title') +chrome.docTitle.change(['My application', 'My section']) + +``` + diff --git a/docs/development/core/public/kibana-plugin-public.chromedoctitle.md b/docs/development/core/public/kibana-plugin-public.chromedoctitle.md new file mode 100644 index 0000000000000..3c6cfab486288 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.chromedoctitle.md @@ -0,0 +1,39 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ChromeDocTitle](./kibana-plugin-public.chromedoctitle.md) + +## ChromeDocTitle interface + +APIs for accessing and updating the document title. + +Signature: + +```typescript +export interface ChromeDocTitle +``` + +## Methods + +| Method | Description | +| --- | --- | +| [change(newTitle)](./kibana-plugin-public.chromedoctitle.change.md) | Changes the current document title. | +| [reset()](./kibana-plugin-public.chromedoctitle.reset.md) | Resets the document title to it's initial value. (meaning the one present in the title meta at application load.) | + +## Example 1 + +How to change the title of the document + +```ts +chrome.docTitle.change('My application') + +``` + +## Example 2 + +How to reset the title of the document to it's initial value + +```ts +chrome.docTitle.reset() + +``` + diff --git a/docs/development/core/public/kibana-plugin-public.chromedoctitle.reset.md b/docs/development/core/public/kibana-plugin-public.chromedoctitle.reset.md new file mode 100644 index 0000000000000..4b4c6f573e006 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.chromedoctitle.reset.md @@ -0,0 +1,17 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ChromeDocTitle](./kibana-plugin-public.chromedoctitle.md) > [reset](./kibana-plugin-public.chromedoctitle.reset.md) + +## ChromeDocTitle.reset() method + +Resets the document title to it's initial value. (meaning the one present in the title meta at application load.) + +Signature: + +```typescript +reset(): void; +``` +Returns: + +`void` + diff --git a/docs/development/core/public/kibana-plugin-public.chromestart.doctitle.md b/docs/development/core/public/kibana-plugin-public.chromestart.doctitle.md new file mode 100644 index 0000000000000..71eda64c24646 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.chromestart.doctitle.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ChromeStart](./kibana-plugin-public.chromestart.md) > [docTitle](./kibana-plugin-public.chromestart.doctitle.md) + +## ChromeStart.docTitle property + +APIs for accessing and updating the document title. + +Signature: + +```typescript +docTitle: ChromeDocTitle; +``` diff --git a/docs/development/core/public/kibana-plugin-public.chromestart.md b/docs/development/core/public/kibana-plugin-public.chromestart.md index bc41aa10cce8f..153e06d591404 100644 --- a/docs/development/core/public/kibana-plugin-public.chromestart.md +++ b/docs/development/core/public/kibana-plugin-public.chromestart.md @@ -16,6 +16,7 @@ export interface ChromeStart | Property | Type | Description | | --- | --- | --- | +| [docTitle](./kibana-plugin-public.chromestart.doctitle.md) | ChromeDocTitle | APIs for accessing and updating the document title. | | [navControls](./kibana-plugin-public.chromestart.navcontrols.md) | ChromeNavControls | [APIs](./kibana-plugin-public.chromenavcontrols.md) for registering new controls to be displayed in the navigation bar. | | [navLinks](./kibana-plugin-public.chromestart.navlinks.md) | ChromeNavLinks | [APIs](./kibana-plugin-public.chromenavlinks.md) for manipulating nav links. | | [recentlyAccessed](./kibana-plugin-public.chromestart.recentlyaccessed.md) | ChromeRecentlyAccessed | [APIs](./kibana-plugin-public.chromerecentlyaccessed.md) for recently accessed history. | diff --git a/docs/development/core/public/kibana-plugin-public.httpservicebase.anonymouspaths.md b/docs/development/core/public/kibana-plugin-public.httpservicebase.anonymouspaths.md new file mode 100644 index 0000000000000..e94757c5eb031 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.httpservicebase.anonymouspaths.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) > [anonymousPaths](./kibana-plugin-public.httpservicebase.anonymouspaths.md) + +## HttpServiceBase.anonymousPaths property + +APIs for denoting certain paths for not requiring authentication + +Signature: + +```typescript +anonymousPaths: IAnonymousPaths; +``` diff --git a/docs/development/core/public/kibana-plugin-public.httpservicebase.md b/docs/development/core/public/kibana-plugin-public.httpservicebase.md index a1eef3db42b7d..9ea77c95b343e 100644 --- a/docs/development/core/public/kibana-plugin-public.httpservicebase.md +++ b/docs/development/core/public/kibana-plugin-public.httpservicebase.md @@ -15,6 +15,7 @@ export interface HttpServiceBase | Property | Type | Description | | --- | --- | --- | +| [anonymousPaths](./kibana-plugin-public.httpservicebase.anonymouspaths.md) | IAnonymousPaths | APIs for denoting certain paths for not requiring authentication | | [basePath](./kibana-plugin-public.httpservicebase.basepath.md) | IBasePath | APIs for manipulating the basePath on URL segments. | | [delete](./kibana-plugin-public.httpservicebase.delete.md) | HttpHandler | Makes an HTTP request with the DELETE method. See [HttpHandler](./kibana-plugin-public.httphandler.md) for options. | | [fetch](./kibana-plugin-public.httpservicebase.fetch.md) | HttpHandler | Makes an HTTP request. Defaults to a GET request unless overriden. See [HttpHandler](./kibana-plugin-public.httphandler.md) for options. | diff --git a/docs/development/core/public/kibana-plugin-public.ianonymouspaths.isanonymous.md b/docs/development/core/public/kibana-plugin-public.ianonymouspaths.isanonymous.md new file mode 100644 index 0000000000000..d6be78e1e725b --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.ianonymouspaths.isanonymous.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [IAnonymousPaths](./kibana-plugin-public.ianonymouspaths.md) > [isAnonymous](./kibana-plugin-public.ianonymouspaths.isanonymous.md) + +## IAnonymousPaths.isAnonymous() method + +Determines whether the provided path doesn't require authentication. `path` should include the current basePath. + +Signature: + +```typescript +isAnonymous(path: string): boolean; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| path | string | | + +Returns: + +`boolean` + diff --git a/docs/development/core/public/kibana-plugin-public.ianonymouspaths.md b/docs/development/core/public/kibana-plugin-public.ianonymouspaths.md new file mode 100644 index 0000000000000..1290df28780cf --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.ianonymouspaths.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [IAnonymousPaths](./kibana-plugin-public.ianonymouspaths.md) + +## IAnonymousPaths interface + +APIs for denoting paths as not requiring authentication + +Signature: + +```typescript +export interface IAnonymousPaths +``` + +## Methods + +| Method | Description | +| --- | --- | +| [isAnonymous(path)](./kibana-plugin-public.ianonymouspaths.isanonymous.md) | Determines whether the provided path doesn't require authentication. path should include the current basePath. | +| [register(path)](./kibana-plugin-public.ianonymouspaths.register.md) | Register path as not requiring authentication. path should not include the current basePath. | + diff --git a/docs/development/core/public/kibana-plugin-public.ianonymouspaths.register.md b/docs/development/core/public/kibana-plugin-public.ianonymouspaths.register.md new file mode 100644 index 0000000000000..3ab9bf438aa16 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.ianonymouspaths.register.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [IAnonymousPaths](./kibana-plugin-public.ianonymouspaths.md) > [register](./kibana-plugin-public.ianonymouspaths.register.md) + +## IAnonymousPaths.register() method + +Register `path` as not requiring authentication. `path` should not include the current basePath. + +Signature: + +```typescript +register(path: string): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| path | string | | + +Returns: + +`void` + diff --git a/docs/development/core/public/kibana-plugin-public.md b/docs/development/core/public/kibana-plugin-public.md index e787621c3aaf9..df0b963e2b627 100644 --- a/docs/development/core/public/kibana-plugin-public.md +++ b/docs/development/core/public/kibana-plugin-public.md @@ -32,6 +32,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [Capabilities](./kibana-plugin-public.capabilities.md) | The read-only set of capabilities available for the current UI session. Capabilities are simple key-value pairs of (string, boolean), where the string denotes the capability ID, and the boolean is a flag indicating if the capability is enabled or disabled. | | [ChromeBadge](./kibana-plugin-public.chromebadge.md) | | | [ChromeBrand](./kibana-plugin-public.chromebrand.md) | | +| [ChromeDocTitle](./kibana-plugin-public.chromedoctitle.md) | APIs for accessing and updating the document title. | | [ChromeNavControl](./kibana-plugin-public.chromenavcontrol.md) | | | [ChromeNavControls](./kibana-plugin-public.chromenavcontrols.md) | [APIs](./kibana-plugin-public.chromenavcontrols.md) for registering new controls to be displayed in the navigation bar. | | [ChromeNavLink](./kibana-plugin-public.chromenavlink.md) | | @@ -57,6 +58,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [HttpResponse](./kibana-plugin-public.httpresponse.md) | | | [HttpServiceBase](./kibana-plugin-public.httpservicebase.md) | | | [I18nStart](./kibana-plugin-public.i18nstart.md) | I18nStart.Context is required by any localizable React component from @kbn/i18n and @elastic/eui packages and is supposed to be used as the topmost component for any i18n-compatible React tree. | +| [IAnonymousPaths](./kibana-plugin-public.ianonymouspaths.md) | APIs for denoting paths as not requiring authentication | | [IBasePath](./kibana-plugin-public.ibasepath.md) | APIs for manipulating the basePath on URL segments. | | [IContextContainer](./kibana-plugin-public.icontextcontainer.md) | An object that handles registration of context providers and configuring handlers with context. | | [IHttpFetchError](./kibana-plugin-public.ihttpfetcherror.md) | | @@ -119,5 +121,5 @@ The plugin integrates with the core system via lifecycle events: `setup` | [ToastInputFields](./kibana-plugin-public.toastinputfields.md) | Allowed fields for [ToastInput](./kibana-plugin-public.toastinput.md). | | [ToastsSetup](./kibana-plugin-public.toastssetup.md) | [IToasts](./kibana-plugin-public.itoasts.md) | | [ToastsStart](./kibana-plugin-public.toastsstart.md) | [IToasts](./kibana-plugin-public.itoasts.md) | -| [UiSettingsClientContract](./kibana-plugin-public.uisettingsclientcontract.md) | [UiSettingsClient](./kibana-plugin-public.uisettingsclient.md) | +| [UiSettingsClientContract](./kibana-plugin-public.uisettingsclientcontract.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. [UiSettingsClient](./kibana-plugin-public.uisettingsclient.md) | diff --git a/docs/development/core/public/kibana-plugin-public.uisettingsclient.getall.md b/docs/development/core/public/kibana-plugin-public.uisettingsclient.getall.md index 5eaf06e7dd682..06daf8e8151cd 100644 --- a/docs/development/core/public/kibana-plugin-public.uisettingsclient.getall.md +++ b/docs/development/core/public/kibana-plugin-public.uisettingsclient.getall.md @@ -9,9 +9,9 @@ Gets the metadata about all uiSettings, including the type, default value, and u Signature: ```typescript -getAll(): UiSettingsState; +getAll(): Record>; ``` Returns: -`UiSettingsState` +`Record>` diff --git a/docs/development/core/public/kibana-plugin-public.uisettingsclientcontract.md b/docs/development/core/public/kibana-plugin-public.uisettingsclientcontract.md index 6eda1fd3274c6..7173386d88265 100644 --- a/docs/development/core/public/kibana-plugin-public.uisettingsclientcontract.md +++ b/docs/development/core/public/kibana-plugin-public.uisettingsclientcontract.md @@ -4,7 +4,7 @@ ## UiSettingsClientContract type -[UiSettingsClient](./kibana-plugin-public.uisettingsclient.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. [UiSettingsClient](./kibana-plugin-public.uisettingsclient.md) Signature: diff --git a/docs/development/core/server/kibana-plugin-server.coresetup.md b/docs/development/core/server/kibana-plugin-server.coresetup.md index a6dda69fd154e..c51459bc41a43 100644 --- a/docs/development/core/server/kibana-plugin-server.coresetup.md +++ b/docs/development/core/server/kibana-plugin-server.coresetup.md @@ -19,4 +19,5 @@ export interface CoreSetup | [context](./kibana-plugin-server.coresetup.context.md) | ContextSetup | [ContextSetup](./kibana-plugin-server.contextsetup.md) | | [elasticsearch](./kibana-plugin-server.coresetup.elasticsearch.md) | ElasticsearchServiceSetup | [ElasticsearchServiceSetup](./kibana-plugin-server.elasticsearchservicesetup.md) | | [http](./kibana-plugin-server.coresetup.http.md) | HttpServiceSetup | [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) | +| [uiSettings](./kibana-plugin-server.coresetup.uisettings.md) | UiSettingsServiceSetup | [UiSettingsServiceSetup](./kibana-plugin-server.uisettingsservicesetup.md) | diff --git a/docs/development/core/server/kibana-plugin-server.coresetup.uisettings.md b/docs/development/core/server/kibana-plugin-server.coresetup.uisettings.md new file mode 100644 index 0000000000000..54120d7c3fa8d --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.coresetup.uisettings.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CoreSetup](./kibana-plugin-server.coresetup.md) > [uiSettings](./kibana-plugin-server.coresetup.uisettings.md) + +## CoreSetup.uiSettings property + +[UiSettingsServiceSetup](./kibana-plugin-server.uisettingsservicesetup.md) + +Signature: + +```typescript +uiSettings: UiSettingsServiceSetup; +``` diff --git a/docs/development/core/server/kibana-plugin-server.iuisettingsclient.getdefaults.md b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.getdefaults.md deleted file mode 100644 index 29faa6d945b43..0000000000000 --- a/docs/development/core/server/kibana-plugin-server.iuisettingsclient.getdefaults.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [IUiSettingsClient](./kibana-plugin-server.iuisettingsclient.md) > [getDefaults](./kibana-plugin-server.iuisettingsclient.getdefaults.md) - -## IUiSettingsClient.getDefaults property - -Returns uiSettings default values [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) - -Signature: - -```typescript -getDefaults: () => Record; -``` diff --git a/docs/development/core/server/kibana-plugin-server.iuisettingsclient.getregistered.md b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.getregistered.md new file mode 100644 index 0000000000000..16ae4c3dd8b36 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.getregistered.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [IUiSettingsClient](./kibana-plugin-server.iuisettingsclient.md) > [getRegistered](./kibana-plugin-server.iuisettingsclient.getregistered.md) + +## IUiSettingsClient.getRegistered property + +Returns registered uiSettings values [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) + +Signature: + +```typescript +getRegistered: () => Readonly>; +``` diff --git a/docs/development/core/server/kibana-plugin-server.iuisettingsclient.getuserprovided.md b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.getuserprovided.md index 9a449b64ed5d0..134039cfa91f3 100644 --- a/docs/development/core/server/kibana-plugin-server.iuisettingsclient.getuserprovided.md +++ b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.getuserprovided.md @@ -9,8 +9,5 @@ Retrieves a set of all uiSettings values set by the user. Signature: ```typescript -getUserProvided: () => Promise>; +getUserProvided: () => Promise>>; ``` diff --git a/docs/development/core/server/kibana-plugin-server.iuisettingsclient.md b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.md index 142f33d27c385..a4697ddbbb85e 100644 --- a/docs/development/core/server/kibana-plugin-server.iuisettingsclient.md +++ b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.md @@ -4,7 +4,7 @@ ## IUiSettingsClient interface -Service that provides access to the UiSettings stored in elasticsearch. +Server-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. Signature: @@ -18,8 +18,8 @@ export interface IUiSettingsClient | --- | --- | --- | | [get](./kibana-plugin-server.iuisettingsclient.get.md) | <T extends SavedObjectAttribute = any>(key: string) => Promise<T> | Retrieves uiSettings values set by the user with fallbacks to default values if not specified. | | [getAll](./kibana-plugin-server.iuisettingsclient.getall.md) | <T extends SavedObjectAttribute = any>() => Promise<Record<string, T>> | Retrieves a set of all uiSettings values set by the user with fallbacks to default values if not specified. | -| [getDefaults](./kibana-plugin-server.iuisettingsclient.getdefaults.md) | () => Record<string, UiSettingsParams> | Returns uiSettings default values [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) | -| [getUserProvided](./kibana-plugin-server.iuisettingsclient.getuserprovided.md) | <T extends SavedObjectAttribute = any>() => Promise<Record<string, {
userValue?: T;
isOverridden?: boolean;
}>> | Retrieves a set of all uiSettings values set by the user. | +| [getRegistered](./kibana-plugin-server.iuisettingsclient.getregistered.md) | () => Readonly<Record<string, UiSettingsParams>> | Returns registered uiSettings values [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) | +| [getUserProvided](./kibana-plugin-server.iuisettingsclient.getuserprovided.md) | <T extends SavedObjectAttribute = any>() => Promise<Record<string, UserProvidedValues<T>>> | Retrieves a set of all uiSettings values set by the user. | | [isOverridden](./kibana-plugin-server.iuisettingsclient.isoverridden.md) | (key: string) => boolean | Shows whether the uiSettings value set by the user. | | [remove](./kibana-plugin-server.iuisettingsclient.remove.md) | (key: string) => Promise<void> | Removes uiSettings value by key. | | [removeMany](./kibana-plugin-server.iuisettingsclient.removemany.md) | (keys: string[]) => Promise<void> | Removes multiple uiSettings values by keys. | diff --git a/docs/development/core/server/kibana-plugin-server.md b/docs/development/core/server/kibana-plugin-server.md index 2f81afacf4bb4..9907750b8742f 100644 --- a/docs/development/core/server/kibana-plugin-server.md +++ b/docs/development/core/server/kibana-plugin-server.md @@ -63,7 +63,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [IKibanaSocket](./kibana-plugin-server.ikibanasocket.md) | A tiny abstraction for TCP socket. | | [IndexSettingsDeprecationInfo](./kibana-plugin-server.indexsettingsdeprecationinfo.md) | | | [IRouter](./kibana-plugin-server.irouter.md) | Registers route handlers for specified resource path and method. See [RouteConfig](./kibana-plugin-server.routeconfig.md) and [RequestHandler](./kibana-plugin-server.requesthandler.md) for more information about arguments to route registrations. | -| [IUiSettingsClient](./kibana-plugin-server.iuisettingsclient.md) | Service that provides access to the UiSettings stored in elasticsearch. | +| [IUiSettingsClient](./kibana-plugin-server.iuisettingsclient.md) | Server-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. | | [KibanaRequestRoute](./kibana-plugin-server.kibanarequestroute.md) | Request specific route information exposed to a handler. | | [LegacyRequest](./kibana-plugin-server.legacyrequest.md) | | | [LegacyServiceSetupDeps](./kibana-plugin-server.legacyservicesetupdeps.md) | | @@ -90,10 +90,12 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SavedObjectsBulkGetObject](./kibana-plugin-server.savedobjectsbulkgetobject.md) | | | [SavedObjectsBulkResponse](./kibana-plugin-server.savedobjectsbulkresponse.md) | | | [SavedObjectsBulkUpdateObject](./kibana-plugin-server.savedobjectsbulkupdateobject.md) | | +| [SavedObjectsBulkUpdateOptions](./kibana-plugin-server.savedobjectsbulkupdateoptions.md) | | | [SavedObjectsBulkUpdateResponse](./kibana-plugin-server.savedobjectsbulkupdateresponse.md) | | | [SavedObjectsClientProviderOptions](./kibana-plugin-server.savedobjectsclientprovideroptions.md) | Options to control the creation of the Saved Objects Client. | | [SavedObjectsClientWrapperOptions](./kibana-plugin-server.savedobjectsclientwrapperoptions.md) | Options passed to each SavedObjectsClientWrapperFactory to aid in creating the wrapper instance. | | [SavedObjectsCreateOptions](./kibana-plugin-server.savedobjectscreateoptions.md) | | +| [SavedObjectsDeleteOptions](./kibana-plugin-server.savedobjectsdeleteoptions.md) | | | [SavedObjectsExportOptions](./kibana-plugin-server.savedobjectsexportoptions.md) | Options controlling the export operation. | | [SavedObjectsExportResultDetails](./kibana-plugin-server.savedobjectsexportresultdetails.md) | Structure of the export result details entry | | [SavedObjectsFindOptions](./kibana-plugin-server.savedobjectsfindoptions.md) | | @@ -116,6 +118,8 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SessionStorageCookieOptions](./kibana-plugin-server.sessionstoragecookieoptions.md) | Configuration used to create HTTP session storage based on top of cookie mechanism. | | [SessionStorageFactory](./kibana-plugin-server.sessionstoragefactory.md) | SessionStorage factory to bind one to an incoming request | | [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) | UiSettings parameters defined by the plugins. | +| [UiSettingsServiceSetup](./kibana-plugin-server.uisettingsservicesetup.md) | | +| [UserProvidedValues](./kibana-plugin-server.userprovidedvalues.md) | Describes the values explicitly set by user. | ## Variables @@ -149,6 +153,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [LifecycleResponseFactory](./kibana-plugin-server.lifecycleresponsefactory.md) | Creates an object containing redirection or error response with error details, HTTP headers, and other data transmitted to the client. | | [MIGRATION\_ASSISTANCE\_INDEX\_ACTION](./kibana-plugin-server.migration_assistance_index_action.md) | | | [MIGRATION\_DEPRECATION\_LEVEL](./kibana-plugin-server.migration_deprecation_level.md) | | +| [MutatingOperationRefreshSetting](./kibana-plugin-server.mutatingoperationrefreshsetting.md) | Elasticsearch Refresh setting for mutating operation | | [OnPostAuthHandler](./kibana-plugin-server.onpostauthhandler.md) | See [OnPostAuthToolkit](./kibana-plugin-server.onpostauthtoolkit.md). | | [OnPreAuthHandler](./kibana-plugin-server.onpreauthhandler.md) | See [OnPreAuthToolkit](./kibana-plugin-server.onpreauthtoolkit.md). | | [PluginInitializer](./kibana-plugin-server.plugininitializer.md) | The plugin export at the root of a plugin's server directory should conform to this interface. | diff --git a/docs/development/core/server/kibana-plugin-server.mutatingoperationrefreshsetting.md b/docs/development/core/server/kibana-plugin-server.mutatingoperationrefreshsetting.md new file mode 100644 index 0000000000000..94c8fa8c22ef6 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.mutatingoperationrefreshsetting.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [MutatingOperationRefreshSetting](./kibana-plugin-server.mutatingoperationrefreshsetting.md) + +## MutatingOperationRefreshSetting type + +Elasticsearch Refresh setting for mutating operation + +Signature: + +```typescript +export declare type MutatingOperationRefreshSetting = boolean | 'wait_for'; +``` diff --git a/docs/development/core/server/kibana-plugin-server.requesthandlercontext.core.md b/docs/development/core/server/kibana-plugin-server.requesthandlercontext.core.md index e1ea358320b9a..2d8b27ecb6c67 100644 --- a/docs/development/core/server/kibana-plugin-server.requesthandlercontext.core.md +++ b/docs/development/core/server/kibana-plugin-server.requesthandlercontext.core.md @@ -15,5 +15,8 @@ core: { dataClient: IScopedClusterClient; adminClient: IScopedClusterClient; }; + uiSettings: { + client: IUiSettingsClient; + }; }; ``` diff --git a/docs/development/core/server/kibana-plugin-server.requesthandlercontext.md b/docs/development/core/server/kibana-plugin-server.requesthandlercontext.md index 37a40f98adef3..c9fc80596efa9 100644 --- a/docs/development/core/server/kibana-plugin-server.requesthandlercontext.md +++ b/docs/development/core/server/kibana-plugin-server.requesthandlercontext.md @@ -18,5 +18,5 @@ export interface RequestHandlerContext | Property | Type | Description | | --- | --- | --- | -| [core](./kibana-plugin-server.requesthandlercontext.core.md) | {
savedObjects: {
client: SavedObjectsClientContract;
};
elasticsearch: {
dataClient: IScopedClusterClient;
adminClient: IScopedClusterClient;
};
} | | +| [core](./kibana-plugin-server.requesthandlercontext.core.md) | {
savedObjects: {
client: SavedObjectsClientContract;
};
elasticsearch: {
dataClient: IScopedClusterClient;
adminClient: IScopedClusterClient;
};
uiSettings: {
client: IUiSettingsClient;
};
} | | diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsbulkupdateoptions.md b/docs/development/core/server/kibana-plugin-server.savedobjectsbulkupdateoptions.md new file mode 100644 index 0000000000000..920a6ca224df4 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsbulkupdateoptions.md @@ -0,0 +1,19 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsBulkUpdateOptions](./kibana-plugin-server.savedobjectsbulkupdateoptions.md) + +## SavedObjectsBulkUpdateOptions interface + + +Signature: + +```typescript +export interface SavedObjectsBulkUpdateOptions extends SavedObjectsBaseOptions +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [refresh](./kibana-plugin-server.savedobjectsbulkupdateoptions.refresh.md) | MutatingOperationRefreshSetting | The Elasticsearch Refresh setting for this operation | + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsbulkupdateoptions.refresh.md b/docs/development/core/server/kibana-plugin-server.savedobjectsbulkupdateoptions.refresh.md new file mode 100644 index 0000000000000..35e9e6483da10 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsbulkupdateoptions.refresh.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsBulkUpdateOptions](./kibana-plugin-server.savedobjectsbulkupdateoptions.md) > [refresh](./kibana-plugin-server.savedobjectsbulkupdateoptions.refresh.md) + +## SavedObjectsBulkUpdateOptions.refresh property + +The Elasticsearch Refresh setting for this operation + +Signature: + +```typescript +refresh?: MutatingOperationRefreshSetting; +``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsclient.bulkupdate.md b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.bulkupdate.md index 107e71959f706..30db524ffa02c 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsclient.bulkupdate.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.bulkupdate.md @@ -9,7 +9,7 @@ Bulk Updates multiple SavedObject at once Signature: ```typescript -bulkUpdate(objects: Array>, options?: SavedObjectsBaseOptions): Promise>; +bulkUpdate(objects: Array>, options?: SavedObjectsBulkUpdateOptions): Promise>; ``` ## Parameters @@ -17,7 +17,7 @@ bulkUpdate(objects: ArrayArray<SavedObjectsBulkUpdateObject<T>> | | -| options | SavedObjectsBaseOptions | | +| options | SavedObjectsBulkUpdateOptions | | Returns: diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsclient.delete.md b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.delete.md index 657f56d591e77..c20c7e886490a 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsclient.delete.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.delete.md @@ -9,7 +9,7 @@ Deletes a SavedObject Signature: ```typescript -delete(type: string, id: string, options?: SavedObjectsBaseOptions): Promise<{}>; +delete(type: string, id: string, options?: SavedObjectsDeleteOptions): Promise<{}>; ``` ## Parameters @@ -18,7 +18,7 @@ delete(type: string, id: string, options?: SavedObjectsBaseOptions): Promise<{}> | --- | --- | --- | | type | string | | | id | string | | -| options | SavedObjectsBaseOptions | | +| options | SavedObjectsDeleteOptions | | Returns: diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectscreateoptions.md b/docs/development/core/server/kibana-plugin-server.savedobjectscreateoptions.md index 16549e420ac01..e4ad636056915 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectscreateoptions.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectscreateoptions.md @@ -19,4 +19,5 @@ export interface SavedObjectsCreateOptions extends SavedObjectsBaseOptions | [migrationVersion](./kibana-plugin-server.savedobjectscreateoptions.migrationversion.md) | SavedObjectsMigrationVersion | Information about the migrations that have been applied to this SavedObject. When Kibana starts up, KibanaMigrator detects outdated documents and migrates them based on this value. For each migration that has been applied, the plugin's name is used as a key and the latest migration version as the value. | | [overwrite](./kibana-plugin-server.savedobjectscreateoptions.overwrite.md) | boolean | Overwrite existing documents (defaults to false) | | [references](./kibana-plugin-server.savedobjectscreateoptions.references.md) | SavedObjectReference[] | | +| [refresh](./kibana-plugin-server.savedobjectscreateoptions.refresh.md) | MutatingOperationRefreshSetting | The Elasticsearch Refresh setting for this operation | diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectscreateoptions.refresh.md b/docs/development/core/server/kibana-plugin-server.savedobjectscreateoptions.refresh.md new file mode 100644 index 0000000000000..785874a12c8c4 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectscreateoptions.refresh.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsCreateOptions](./kibana-plugin-server.savedobjectscreateoptions.md) > [refresh](./kibana-plugin-server.savedobjectscreateoptions.refresh.md) + +## SavedObjectsCreateOptions.refresh property + +The Elasticsearch Refresh setting for this operation + +Signature: + +```typescript +refresh?: MutatingOperationRefreshSetting; +``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsdeleteoptions.md b/docs/development/core/server/kibana-plugin-server.savedobjectsdeleteoptions.md new file mode 100644 index 0000000000000..2c641ba5cc8d8 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsdeleteoptions.md @@ -0,0 +1,19 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsDeleteOptions](./kibana-plugin-server.savedobjectsdeleteoptions.md) + +## SavedObjectsDeleteOptions interface + + +Signature: + +```typescript +export interface SavedObjectsDeleteOptions extends SavedObjectsBaseOptions +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [refresh](./kibana-plugin-server.savedobjectsdeleteoptions.refresh.md) | MutatingOperationRefreshSetting | The Elasticsearch Refresh setting for this operation | + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsdeleteoptions.refresh.md b/docs/development/core/server/kibana-plugin-server.savedobjectsdeleteoptions.refresh.md new file mode 100644 index 0000000000000..782c52956f297 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsdeleteoptions.refresh.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsDeleteOptions](./kibana-plugin-server.savedobjectsdeleteoptions.md) > [refresh](./kibana-plugin-server.savedobjectsdeleteoptions.refresh.md) + +## SavedObjectsDeleteOptions.refresh property + +The Elasticsearch Refresh setting for this operation + +Signature: + +```typescript +refresh?: MutatingOperationRefreshSetting; +``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsupdateoptions.md b/docs/development/core/server/kibana-plugin-server.savedobjectsupdateoptions.md index 7fcd362e937a0..49e8946ad2826 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsupdateoptions.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsupdateoptions.md @@ -16,5 +16,6 @@ export interface SavedObjectsUpdateOptions extends SavedObjectsBaseOptions | Property | Type | Description | | --- | --- | --- | | [references](./kibana-plugin-server.savedobjectsupdateoptions.references.md) | SavedObjectReference[] | A reference to another saved object. | +| [refresh](./kibana-plugin-server.savedobjectsupdateoptions.refresh.md) | MutatingOperationRefreshSetting | The Elasticsearch Refresh setting for this operation | | [version](./kibana-plugin-server.savedobjectsupdateoptions.version.md) | string | An opaque version number which changes on each successful write operation. Can be used for implementing optimistic concurrency control. | diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsupdateoptions.refresh.md b/docs/development/core/server/kibana-plugin-server.savedobjectsupdateoptions.refresh.md new file mode 100644 index 0000000000000..bb1142c242012 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsupdateoptions.refresh.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsUpdateOptions](./kibana-plugin-server.savedobjectsupdateoptions.md) > [refresh](./kibana-plugin-server.savedobjectsupdateoptions.refresh.md) + +## SavedObjectsUpdateOptions.refresh property + +The Elasticsearch Refresh setting for this operation + +Signature: + +```typescript +refresh?: MutatingOperationRefreshSetting; +``` diff --git a/docs/development/core/server/kibana-plugin-server.uisettingsparams.category.md b/docs/development/core/server/kibana-plugin-server.uisettingsparams.category.md index 47aedbfbf2810..6bf1b17dc947a 100644 --- a/docs/development/core/server/kibana-plugin-server.uisettingsparams.category.md +++ b/docs/development/core/server/kibana-plugin-server.uisettingsparams.category.md @@ -9,5 +9,5 @@ used to group the configured setting in the UI Signature: ```typescript -category: string[]; +category?: string[]; ``` diff --git a/docs/development/core/server/kibana-plugin-server.uisettingsparams.description.md b/docs/development/core/server/kibana-plugin-server.uisettingsparams.description.md index 8d8887285ae2e..6a203629f5425 100644 --- a/docs/development/core/server/kibana-plugin-server.uisettingsparams.description.md +++ b/docs/development/core/server/kibana-plugin-server.uisettingsparams.description.md @@ -9,5 +9,5 @@ description provided to a user in UI Signature: ```typescript -description: string; +description?: string; ``` diff --git a/docs/development/core/server/kibana-plugin-server.uisettingsparams.md b/docs/development/core/server/kibana-plugin-server.uisettingsparams.md index 275111c05eff9..a38499e8f37dd 100644 --- a/docs/development/core/server/kibana-plugin-server.uisettingsparams.md +++ b/docs/development/core/server/kibana-plugin-server.uisettingsparams.md @@ -20,7 +20,7 @@ export interface UiSettingsParams | [description](./kibana-plugin-server.uisettingsparams.description.md) | string | description provided to a user in UI | | [name](./kibana-plugin-server.uisettingsparams.name.md) | string | title in the UI | | [optionLabels](./kibana-plugin-server.uisettingsparams.optionlabels.md) | Record<string, string> | text labels for 'select' type UI element | -| [options](./kibana-plugin-server.uisettingsparams.options.md) | string[] | a range of valid values | +| [options](./kibana-plugin-server.uisettingsparams.options.md) | string[] | array of permitted values for this setting | | [readonly](./kibana-plugin-server.uisettingsparams.readonly.md) | boolean | a flag indicating that value cannot be changed | | [requiresPageReload](./kibana-plugin-server.uisettingsparams.requirespagereload.md) | boolean | a flag indicating whether new value applying requires page reloading | | [type](./kibana-plugin-server.uisettingsparams.type.md) | UiSettingsType | defines a type of UI element [UiSettingsType](./kibana-plugin-server.uisettingstype.md) | diff --git a/docs/development/core/server/kibana-plugin-server.uisettingsparams.name.md b/docs/development/core/server/kibana-plugin-server.uisettingsparams.name.md index 2b414eefffed2..07905ca7de20a 100644 --- a/docs/development/core/server/kibana-plugin-server.uisettingsparams.name.md +++ b/docs/development/core/server/kibana-plugin-server.uisettingsparams.name.md @@ -9,5 +9,5 @@ title in the UI Signature: ```typescript -name: string; +name?: string; ``` diff --git a/docs/development/core/server/kibana-plugin-server.uisettingsparams.options.md b/docs/development/core/server/kibana-plugin-server.uisettingsparams.options.md index 71eecdfabc4a0..2220feab74ffd 100644 --- a/docs/development/core/server/kibana-plugin-server.uisettingsparams.options.md +++ b/docs/development/core/server/kibana-plugin-server.uisettingsparams.options.md @@ -4,7 +4,7 @@ ## UiSettingsParams.options property -a range of valid values +array of permitted values for this setting Signature: diff --git a/docs/development/core/server/kibana-plugin-server.uisettingsparams.value.md b/docs/development/core/server/kibana-plugin-server.uisettingsparams.value.md index 455756899ecfc..397498ccf5c11 100644 --- a/docs/development/core/server/kibana-plugin-server.uisettingsparams.value.md +++ b/docs/development/core/server/kibana-plugin-server.uisettingsparams.value.md @@ -9,5 +9,5 @@ default value to fall back to if a user doesn't provide any Signature: ```typescript -value: SavedObjectAttribute; +value?: SavedObjectAttribute; ``` diff --git a/docs/development/core/server/kibana-plugin-server.uisettingsservicesetup.md b/docs/development/core/server/kibana-plugin-server.uisettingsservicesetup.md new file mode 100644 index 0000000000000..8dde78f633d88 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.uisettingsservicesetup.md @@ -0,0 +1,19 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [UiSettingsServiceSetup](./kibana-plugin-server.uisettingsservicesetup.md) + +## UiSettingsServiceSetup interface + + +Signature: + +```typescript +export interface UiSettingsServiceSetup +``` + +## Methods + +| Method | Description | +| --- | --- | +| [register(settings)](./kibana-plugin-server.uisettingsservicesetup.register.md) | Sets settings with default values for the uiSettings. | + diff --git a/docs/development/core/server/kibana-plugin-server.uisettingsservicesetup.register.md b/docs/development/core/server/kibana-plugin-server.uisettingsservicesetup.register.md new file mode 100644 index 0000000000000..8091a7cec44aa --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.uisettingsservicesetup.register.md @@ -0,0 +1,28 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [UiSettingsServiceSetup](./kibana-plugin-server.uisettingsservicesetup.md) > [register](./kibana-plugin-server.uisettingsservicesetup.register.md) + +## UiSettingsServiceSetup.register() method + +Sets settings with default values for the uiSettings. + +Signature: + +```typescript +register(settings: Record): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| settings | Record<string, UiSettingsParams> | | + +Returns: + +`void` + +## Example + +setup(core: CoreSetup){ core.uiSettings.register(\[{ foo: { name: i18n.translate('my foo settings'), value: true, description: 'add some awesomeness', }, }\]); } + diff --git a/docs/development/core/server/kibana-plugin-server.userprovidedvalues.isoverridden.md b/docs/development/core/server/kibana-plugin-server.userprovidedvalues.isoverridden.md new file mode 100644 index 0000000000000..01e04b490595d --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.userprovidedvalues.isoverridden.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [UserProvidedValues](./kibana-plugin-server.userprovidedvalues.md) > [isOverridden](./kibana-plugin-server.userprovidedvalues.isoverridden.md) + +## UserProvidedValues.isOverridden property + +Signature: + +```typescript +isOverridden?: boolean; +``` diff --git a/docs/development/core/server/kibana-plugin-server.userprovidedvalues.md b/docs/development/core/server/kibana-plugin-server.userprovidedvalues.md new file mode 100644 index 0000000000000..7b2114404d7f2 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.userprovidedvalues.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [UserProvidedValues](./kibana-plugin-server.userprovidedvalues.md) + +## UserProvidedValues interface + +Describes the values explicitly set by user. + +Signature: + +```typescript +export interface UserProvidedValues +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [isOverridden](./kibana-plugin-server.userprovidedvalues.isoverridden.md) | boolean | | +| [userValue](./kibana-plugin-server.userprovidedvalues.uservalue.md) | T | | + diff --git a/docs/development/core/server/kibana-plugin-server.userprovidedvalues.uservalue.md b/docs/development/core/server/kibana-plugin-server.userprovidedvalues.uservalue.md new file mode 100644 index 0000000000000..59d25651b7697 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.userprovidedvalues.uservalue.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [UserProvidedValues](./kibana-plugin-server.userprovidedvalues.md) > [userValue](./kibana-plugin-server.userprovidedvalues.uservalue.md) + +## UserProvidedValues.userValue property + +Signature: + +```typescript +userValue?: T; +``` diff --git a/docs/getting-started/tutorial-full-experience.asciidoc b/docs/getting-started/tutorial-full-experience.asciidoc index 08f65b0a24091..eafbb7d8f7c91 100644 --- a/docs/getting-started/tutorial-full-experience.asciidoc +++ b/docs/getting-started/tutorial-full-experience.asciidoc @@ -183,7 +183,7 @@ At this point, you're ready to use the Elasticsearch {ref}/docs-bulk.html[bulk] API to load the data sets: [source,shell] -curl -u elastic -H 'Content-Type: application/x-ndjson' -XPOST ':/bank/account/_bulk?pretty' --data-binary @accounts.json +curl -u elastic -H 'Content-Type: application/x-ndjson' -XPOST ':/bank/_bulk?pretty' --data-binary @accounts.json curl -u elastic -H 'Content-Type: application/x-ndjson' -XPOST ':/shakespeare/_bulk?pretty' --data-binary @shakespeare.json curl -u elastic -H 'Content-Type: application/x-ndjson' -XPOST ':/_bulk?pretty' --data-binary @logs.jsonl diff --git a/docs/images/code-blame.png b/docs/images/code-blame.png deleted file mode 100644 index c04e1b12c58f5..0000000000000 Binary files a/docs/images/code-blame.png and /dev/null differ diff --git a/docs/images/code-full-text-search.png b/docs/images/code-full-text-search.png deleted file mode 100644 index 4d4d3d21a24f6..0000000000000 Binary files a/docs/images/code-full-text-search.png and /dev/null differ diff --git a/docs/images/code-history.png b/docs/images/code-history.png deleted file mode 100644 index 853fcc8fa6adc..0000000000000 Binary files a/docs/images/code-history.png and /dev/null differ diff --git a/docs/images/code-import-repo.png b/docs/images/code-import-repo.png deleted file mode 100644 index adc60614c1007..0000000000000 Binary files a/docs/images/code-import-repo.png and /dev/null differ diff --git a/docs/images/code-lang-server-status.png b/docs/images/code-lang-server-status.png deleted file mode 100644 index 3143a876f1c75..0000000000000 Binary files a/docs/images/code-lang-server-status.png and /dev/null differ diff --git a/docs/images/code-lang-server-tab.png b/docs/images/code-lang-server-tab.png deleted file mode 100644 index b25573f30d40b..0000000000000 Binary files a/docs/images/code-lang-server-tab.png and /dev/null differ diff --git a/docs/images/code-quick-search.png b/docs/images/code-quick-search.png deleted file mode 100644 index e6274b77dcbaa..0000000000000 Binary files a/docs/images/code-quick-search.png and /dev/null differ diff --git a/docs/images/code-repo-management.png b/docs/images/code-repo-management.png deleted file mode 100644 index 6e3fe5476b3be..0000000000000 Binary files a/docs/images/code-repo-management.png and /dev/null differ diff --git a/docs/images/code-search-filter.png b/docs/images/code-search-filter.png deleted file mode 100644 index f9b09ad2d71ec..0000000000000 Binary files a/docs/images/code-search-filter.png and /dev/null differ diff --git a/docs/images/code-semantic-nav.png b/docs/images/code-semantic-nav.png deleted file mode 100644 index 664225804ce22..0000000000000 Binary files a/docs/images/code-semantic-nav.png and /dev/null differ diff --git a/docs/images/code-starter-root.png b/docs/images/code-starter-root.png deleted file mode 100644 index a2e3a579fe2f2..0000000000000 Binary files a/docs/images/code-starter-root.png and /dev/null differ diff --git a/docs/images/code-symbol-table.png b/docs/images/code-symbol-table.png deleted file mode 100644 index d3bb4c6eef0c1..0000000000000 Binary files a/docs/images/code-symbol-table.png and /dev/null differ diff --git a/docs/infrastructure/getting-started.asciidoc b/docs/infrastructure/getting-started.asciidoc index 151a8e2928cf8..1c5645f5a6e4e 100644 --- a/docs/infrastructure/getting-started.asciidoc +++ b/docs/infrastructure/getting-started.asciidoc @@ -5,7 +5,7 @@ To get started with the Metrics app in Kibana, you need to start collecting metrics data for your infrastructure. Kibana provides step-by-step instructions to help you add metrics data. -The {infra-guide}[Infrastructure Monitoring Guide] is a good source for more detailed information and instructions. +The {metrics-guide}[Metrics Monitoring Guide] is a good source for more detailed information and instructions. [role="screenshot"] image::infrastructure/images/metrics-add-data.png[Screenshot showing Add metric data to Kibana UI] diff --git a/docs/logs/getting-started.asciidoc b/docs/logs/getting-started.asciidoc index 1ed8798a4b87f..ca09bb34c0e56 100644 --- a/docs/logs/getting-started.asciidoc +++ b/docs/logs/getting-started.asciidoc @@ -5,7 +5,7 @@ To get started with the Logs app in Kibana, you need to start collecting logs data for your infrastructure. Kibana provides step-by-step instructions to help you add logs data. -The {infra-guide}[Infrastructure Monitoring Guide] is a good source for more detailed information and instructions. +The {logs-guide}[Logs Monitoring Guide] is a good source for more detailed information and instructions. [role="screenshot"] image::logs/images/logs-add-data.png[Screenshot showing Add logging data in Kibana] diff --git a/docs/management/snapshot-restore/images/create-policy-example.png b/docs/management/snapshot-restore/images/create-policy-example.png new file mode 100755 index 0000000000000..e871c925f5fd5 Binary files /dev/null and b/docs/management/snapshot-restore/images/create-policy-example.png differ diff --git a/docs/management/snapshot-restore/images/snapshot-retention.png b/docs/management/snapshot-restore/images/snapshot-retention.png new file mode 100755 index 0000000000000..7b390357a21b6 Binary files /dev/null and b/docs/management/snapshot-restore/images/snapshot-retention.png differ diff --git a/docs/management/snapshot-restore/index.asciidoc b/docs/management/snapshot-restore/index.asciidoc index f309b2c17e0ed..f19aaa122675e 100644 --- a/docs/management/snapshot-restore/index.asciidoc +++ b/docs/management/snapshot-restore/index.asciidoc @@ -11,11 +11,11 @@ you can restore a snapshot from the repository. You’ll find *Snapshot and Restore* under *Management > Elasticsearch*. With this UI, you can: -* <> -* <> -* <> -* <> -* <> +* Register a repository for storing your snapshots +* View a list of your snapshots and drill down into details +* Restore data into your cluster from a snapshot +* Create a policy to automate snapshot creation and deletion +* Delete a snapshot to free storage space [role="screenshot"] image:management/snapshot-restore/images/snapshot_list.png["Snapshot list"] @@ -27,28 +27,34 @@ more detailed information. [float] [[kib-snapshot-register-repository]] === Register a repository +A repository is where your snapshots live. You must register a snapshot +repository before you can perform snapshot and restore operations. + +If you don't have a repository, Kibana walks you through the process of +registering one. +{kib} supports three repository types +out of the box: shared file system, read-only URL, and source-only. +For more information on these repositories and their settings, +see {ref}/modules-snapshots.html#snapshots-repositories[Repositories]. +To use other repositories, such as S3, see +{ref}/modules-snapshots.html#_repository_plugins[Repository plugins]. -The *Repositories* view provides an overview of your repositories. -Click a repository name to view its type, number of snapshots, and settings, and also to verify status. + +Once you create a repository, it is listed in the *Repositories* +view. +Click a repository name to view its type, number of snapshots, and settings, +and to verify status. [role="screenshot"] image:management/snapshot-restore/images/repository_list.png["Repository list"] -If you don't have a repository, you're prompted to register one. -{es} supports three repository types -out of the box: shared file system, read-only URL, and source-only. -For more information on these repositories and their settings, -see {ref}/modules-snapshots.html#snapshots-repositories[Repositories]. For an example, -see <>. - -To use other repositories, such as S3, you can install plugins. See -{ref}/modules-snapshots.html#_repository_plugins[Repository plugins]. [float] [[kib-view-snapshot]] === View your snapshots -The *Snapshots* view gives an overview of your snapshots. You can drill down +A snapshot is a backup taken from a running {es} cluster. You'll find an overview of +your snapshots in the *Snapshots* view, and you can drill down into each snapshot for further investigation. [role="screenshot"] @@ -68,18 +74,25 @@ the new data. [[kib-restore-snapshot]] === Restore a snapshot -The *Restore* wizard walks you through the process of restoring a snapshot -into a running cluster. To get started, go to the *Snapshots* view, find the -snapshot, and click the restore icon in the *Actions* column. +The information stored in a snapshot is not tied to a specific +cluster or a cluster name. This enables you to +restore a snapshot made from one cluster to another cluster. You might +use the restore operation to: -You’re presented -options for the restore, including which +* Recover data lost due to a failure +* Migrate a current Elasticsearch cluster to a new version +* Move data from one cluster to another cluster + +To get started, go to the *Snapshots* view, find the +snapshot, and click the restore icon in the *Actions* column. +The Restore wizard presents +options for the restore operation, including which indices to restore and whether to modify the index settings. You can restore an existing index only if it’s closed and has the same number of shards as the index in the snapshot. Once you initiate the restore, you're navigated to the *Restore Status* view, -where you can track the progress. +where you can track the current state for each shard in the snapshot. [role="screenshot"] image:management/snapshot-restore/images/snapshot-restore.png["Snapshot details"] @@ -89,23 +102,28 @@ image:management/snapshot-restore/images/snapshot-restore.png["Snapshot details" [[kib-snapshot-policy]] === Create a snapshot lifecycle policy -You can create policies to schedule automatic snapshots of your cluster. -{ref}/snapshot-lifecycle-management-api.html[Snapshot lifecycle policies] are related -to {ref}/index-lifecycle-management.html[index lifecycle policies]. -However, where an index lifecycle policy applies to a single index, -a snapshot lifecycle policy can span multiple indices. - -For an overview of your policies, open the *Policies* view. -You can drill down into each policy to examine its settings and last successful and failed run. - -If you don’t have any policies, use the *Create policy* wizard. -You’ll define the snapshots and repository, when to take snapshots, and -the settings, such as which indices the snapshot should contain. +Use a {ref}/snapshot-lifecycle-management-api.html[snapshot lifecycle policy] +to automate the creation and deletion +of cluster snapshots. Taking automatic snapshots: + +* Ensures your {es} indices and clusters are backed up on a regular basis +* Ensures a recent and relevant snapshot is available if a situation +arises where a cluster needs to be recovered +* Allows you to manage your snapshots in {kib}, instead of using a +third-party tool + +If you don’t have any snapshot policies, follow the +*Create policy* wizard. It walks you through defining +when and where to take snapshots, the settings you want, +and how long to retain snapshots. [role="screenshot"] -image:management/snapshot-restore/images/create-policy.png["Snapshot details"] +image:management/snapshot-restore/images/snapshot-retention.png["Snapshot details"] + +An overview of your policies is on the *Policies* view. +You can drill down into each policy to examine its settings and last successful and failed run. -You can perform the following actions on a policy: +You can perform the following actions on a snapshot policy: * *Run* a policy immediately without waiting for the scheduled time. This action is useful before an upgrade or before performing maintenance on indices. @@ -113,6 +131,9 @@ This action is useful before an upgrade or before performing maintenance on indi * *Delete* a policy to prevent any future snapshots from being taken. This action does not cancel any currently ongoing snapshots or remove any previously taken snapshots. +[role="screenshot"] +image:management/snapshot-restore/images/create-policy.png["Snapshot details"] + [float] [[kib-delete-snapshot]] === Delete a snapshot @@ -123,16 +144,25 @@ Find the snapshot in the *Snapshots* view and click the trash icon in the and then click *Delete snapshots*. [[snapshot-repositories-example]] -[float] -=== Example: Register a shared file system repository -This example shows how to register a shared file system repository -and store snapshots. +[role="xpack"] +[[snapshot-restore-tutorial]] +=== Tutorial: Snapshot and Restore -[float] -==== Register the repository location -You must register the location of the repository in the `path.repo` setting on +Ready to try *Snapshot and Restore*? In this tutorial, you'll learn to: + +* Register a repository +* Add snapshots to the repository +* Create a snapshot lifecycle policy +* Restore a snapshot + +==== Before you begin + +This example shows you how to register a shared file system repository +and store snapshots. +Before you begin, you must register the location of the repository in the +{ref}/modules-snapshots.html#_shared_file_system_repository[path.repo] setting on your master and data nodes. You can do this in one of two ways: * Edit your `elasticsearch.yml` to include the `path.repo` setting. @@ -142,30 +172,26 @@ your master and data nodes. You can do this in one of two ways: `bin/elasticsearch -E path.repo=/tmp/es-backups` [float] -==== Register the repository +[[register-repo-example]] +==== Register a repository Use *Snapshot and Restore* to register the repository where your snapshots will live. . Go to *Management > Elasticsearch > Snapshot and Restore*. -. Open the *Repositories* view. -. Click *Register a repository*. +. Click *Register a repository* in either the introductory message or *Repository view*. . Enter a name for your repository, for example, `my_backup`. -. Set *Repository type* to Shared file system. +. Select *Shared file system*. + [role="screenshot"] image:management/snapshot-restore/images/register_repo.png["Register repository"] . Click *Next*. -. In *Location*, enter the path to the snapshot repository, `/tmp/es-backups`. -. In *Chunk size*, enter 100mb so that snapshot files are not bigger than that size. -. Use the defaults for all other fields. -. Click *Register*. +. In *File system location*, enter the path to the snapshot repository, `/tmp/es-backups`. +. In *Chunk size*, enter `100mb` so that snapshot files are not bigger than that size. +. Use the defaults for all other fields, and then click *Register*. + Your new repository is listed on the *Repositories* view. -+ -. Click the respository and inspect its details. -+ The repository currently doesn’t have any snapshots. @@ -174,19 +200,105 @@ The repository currently doesn’t have any snapshots. Use the {ref}//modules-snapshots.html#snapshots-take-snapshot[snapshot API] to create a snapshot. . Go to *Dev Tools > Console*. -. Create the snapshot. +. Create the snapshot: ++ +[source,js] +PUT /_snapshot/my_backup/2019-04-25_snapshot?wait_for_completion=true + In this example, the snapshot name is `2019-04-25_snapshot`. You can also use {ref}//date-math-index-names.html[date math expression] for the snapshot name. + [role="screenshot"] image:management/snapshot-restore/images/create_snapshot.png["Create snapshot"] -+ -. Open *Snapshot and Restore*. + +. Return to *Snapshot and Restore*. + Your new snapshot is available in the *Snapshots* view. +[[create-policy-example]] +==== Create a snapshot lifecycle policy + +Now you'll automate the creation and deletion of snapshots +using the repository created in the previous example. + +. Open the *Policies* view. +. Click *Create a policy*. ++ +[role="screenshot"] +image:management/snapshot-restore/images/create-policy-example.png["Create policy wizard"] + +. As you walk through the wizard, enter the following values: ++ +|=== +|*Logistics* | + +|Policy name +|`daily-snapshots` + +|Snapshot name +|`` + +|Schedule +|Every day at 1:30 a.m. + +|Repository +|`my_backup` + +|*Snapshot settings* | +|Indices +|Select the indices to back up. By default, all indices, including system indices, are backed up. +|All other settings +|Use the defaults. +|*Snapshot retention* | + +|Expiration +|`30 days` + +|Snapshots to retain +|Minimum count: `5`, Maximum count: `50` +|=== + +. Review your input, and then click *Create policy*. ++ +Your new policy is listed in the *Policies* view, and you see a summary of its details. + +[[restore-snapshot-example]] +==== Restore a snapshot +Finally, you'll restore indices from an existing snapshot. + +. In the *Snapshots* view, find the snapshot you want to restore, for example `2019-04-25_snapshot`. +. Click the restore icon in the *Actions* column. +. As you walk through the wizard, enter the following values: ++ +|=== +|*Logistics* | + +|Indices +|Toggle to choose specific indices to restore, or leave in place to restore all indices. + +|Rename indices +|Toggle to give your restored indices new names, or leave in place to restore under original index names. + +|All other fields +|Use the defaults. + +|*Index settings* | + +|Modify index settings +|Toggle to overwrite index settings when they are restored, +or leave in place to keep existing settings. + +|Reset index settings +|Toggle to reset index settings back to the default when they are restored, +or leave in place to keep existing settings. +|=== + +. Review your restore settings, and then click *Restore snapshot*. ++ +The operation loads for a few seconds, +and then you’re navigated to *Restore Status*, +where you can monitor the status of your restored indices. diff --git a/docs/maps/heatmap-layer.asciidoc b/docs/maps/heatmap-layer.asciidoc index 9d456c59b69ef..77b6d929a931c 100644 --- a/docs/maps/heatmap-layer.asciidoc +++ b/docs/maps/heatmap-layer.asciidoc @@ -13,6 +13,6 @@ You can create a heat map layer from the following data source: Set *Show as* to *heat map*. The index must contain at least one field mapped as {ref}/geo-point.html[geo_point]. -NOTE: Only count and sum metric aggregations are available with the grid aggregation source and heat map layers. -Mean, median, min, and max are turned off because the heat map will blend nearby values. +NOTE: Only count, sum, unique count metric aggregations are available with the grid aggregation source and heat map layers. +Average, min, and max are turned off because the heat map will blend nearby values. Blending two average values would make the cluster more prominent, even though it just might literally mean that these nearby areas are average. diff --git a/docs/settings/code-settings.asciidoc b/docs/settings/code-settings.asciidoc deleted file mode 100644 index 5dbcb9b3508b8..0000000000000 --- a/docs/settings/code-settings.asciidoc +++ /dev/null @@ -1,51 +0,0 @@ -[role="xpack"] -[[code-settings-kibana]] -=== Code Settings in Kibana -++++ -Code settings -++++ - -Unless you are running multiple Kibana instances as a cluster, you do not need to change any settings to use *Code* by default. If you’d like to change any of the default values, copy and paste the relevant settings below into your `kibana.yml` configuration file. - - -`xpack.code.updateRepoFrequencyMs`:: -Repo update frequency in milliseconds. Defaults to `300000`. - -`xpack.code.indexRepoFrequencyMs`:: -Repo index frequency in milliseconds. Defaults to `86400000`. - -`xpack.code.lsp.verbose`:: -Whether to show more verbose log for language servers. Defaults to `false`. - -`xpack.code.security.enableMavenImport`:: -Whether to support maven. Defaults to `true`. - -`xpack.code.security.enableGradleImport`:: -Whether to support gradle. Defaults to `false`. - -`xpack.code.security.installNodeDependency`:: -Whether to enable node dependency download. Defaults to `true`. - -`xpack.code.security.gitHostWhitelist`:: -Whitelist of hostnames for git clone address. Defaults to `[ 'github.com', 'gitlab.com', 'bitbucket.org', 'gitbox.apache.org', 'eclipse.org',]`. - -`xpack.code.security.gitProtocolWhitelist`:: -Whitelist of protocols for git clone address. Defaults to `[ 'https', 'git', 'ssh' ]`. - -`xpack.code.security.enableGitCertCheck`:: -Whether enable HTTPS certificate check when clone from HTTPS URL. - -`xpack.code.security.enableJavaSecurityManager`:: -Whether enable Java security manager for Java langserver. Defaults to `true`. - -`xpack.code.security.extraJavaRepositoryWhitelist`:: -Whitelist of extra repository to download dependencies for Java language. Defaults to `[]`. - -`xpack.code.maxWorkspace`:: -Maximal number of workspaces each language server allows to span. Defaults to `5`. - -`xpack.code.codeNodeUrl`:: -URL of the Code node. This config is only needed when multiple Kibana instances are set up as a cluster. Defaults to `` - -`xpack.code.verbose`:: -Set this config to `true` to log all events. Defaults to `false` diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index f02cf188d31ad..6e7f939a1d2ab 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -277,8 +277,8 @@ setting specifies the port to use. `server.rewriteBasePath:`:: *Default: deprecated* Specifies whether Kibana should rewrite requests that are prefixed with `server.basePath` or require that they -are rewritten by your reverse proxy. In Kibana 6.3 and earlier, the default is -`false`. In Kibana 7.x, the setting is deprecated. In Kibana 8.0 and later, the +are rewritten by your reverse proxy. In Kibana 6.3 and earlier, the default is +`false`. In Kibana 7.x, the setting is deprecated. In Kibana 8.0 and later, the default is `true`. `server.socketTimeout:`:: *Default: "120000"* The number of milliseconds to wait before closing an @@ -327,7 +327,6 @@ Rollup user interface. include::{docdir}/settings/apm-settings.asciidoc[] -include::{docdir}/settings/code-settings.asciidoc[] include::{docdir}/settings/dev-settings.asciidoc[] include::{docdir}/settings/graph-settings.asciidoc[] include::{docdir}/settings/infrastructure-ui-settings.asciidoc[] diff --git a/docs/user/index.asciidoc b/docs/user/index.asciidoc index 54817a6e8743d..4b29255c50a1d 100644 --- a/docs/user/index.asciidoc +++ b/docs/user/index.asciidoc @@ -20,8 +20,6 @@ include::extend.asciidoc[] include::{kib-repo-dir}/maps/index.asciidoc[] -include::{kib-repo-dir}/code/index.asciidoc[] - include::{kib-repo-dir}/infrastructure/index.asciidoc[] include::{kib-repo-dir}/logs/index.asciidoc[] diff --git a/docs/user/ml/index.asciidoc b/docs/user/ml/index.asciidoc index f4802592f0e07..a5f14ed2cf944 100644 --- a/docs/user/ml/index.asciidoc +++ b/docs/user/ml/index.asciidoc @@ -18,7 +18,16 @@ image::user/ml/images/ml-data-visualizer-sample.jpg[Data Visualizer for sample f experimental[] You can also upload a CSV, NDJSON, or log file (up to 100 MB in size). The *Data Visualizer* identifies the file format and field mappings. You -can then optionally import that data into an {es} index. +can then optionally import that data into an {es} index. + +You need the following permissions to use the Data Visualizer with file upload: + +* cluster privileges: `monitor`, `manage_index_pipelines` +* index privileges: `read`, `manage`, `index` + +For more information, see {ref}/security-privileges.html[Security privileges] +and {ref}/built-in-roles.html[Built-in roles]. + [float] [[xpack-ml-anomalies]] diff --git a/docs/user/security/api-keys/images/api-key-invalidate.png b/docs/user/security/api-keys/images/api-key-invalidate.png new file mode 100755 index 0000000000000..c925679ab24bc Binary files /dev/null and b/docs/user/security/api-keys/images/api-key-invalidate.png differ diff --git a/docs/user/security/api-keys/images/api-keys.png b/docs/user/security/api-keys/images/api-keys.png new file mode 100755 index 0000000000000..df74f245676d9 Binary files /dev/null and b/docs/user/security/api-keys/images/api-keys.png differ diff --git a/docs/user/security/api-keys/index.asciidoc b/docs/user/security/api-keys/index.asciidoc new file mode 100644 index 0000000000000..c00f58cf598e3 --- /dev/null +++ b/docs/user/security/api-keys/index.asciidoc @@ -0,0 +1,86 @@ +[role="xpack"] +[[api-keys]] +=== API Keys + + +API keys enable you to create secondary credentials so that you can send +requests on behalf of the user. Secondary credentials have +the same or lower access rights. + +For example, if you extract data from an {es} cluster on a daily +basis, you might create an API key tied to your credentials, +configure it with minimum access, +and then put the API credentials into a cron job. +Or, you might create API keys to automate ingestion of new data from +remote sources, without a live user interaction. + +You can create API keys from the {kib} Console. To view and invalidate +API keys, use *Management > Security > API Keys*. + +[role="screenshot"] +image:user/security/api-keys/images/api-keys.png["API Keys UI"] + +[float] +[[api-keys-service]] +=== {es} API key service + +The {es} API key service is automatically enabled when you configure +{ref}/configuring-tls.html#tls-http[TLS on the HTTP interface]. +This ensures that clients are unable to send API keys in clear-text. + +When HTTPS connections are not enabled between {kib} and {es}, +you cannot create or manage API keys, and you get an error message. +For more information, see the +{ref}/security-api-create-api-key.html[{es} API key documentation], +or contact your system administrator. + +[float] +[[api-keys-security-privileges]] +=== Security privileges + +You must have the `manage_security`, `manage_api_key`, or the `manage_own_api_key` +cluster privileges to use API keys in {kib}. You can manage roles in +*Management > Security > Roles*, or use the <>. + + +[float] +[[create-api-key]] +=== Create an API key +You can {ref}/security-api-create-api-key.html[create an API key] from +the Kibana Console. For example: + +[source,js] +POST /_security/api_key +{ + "name": "my_api_key", + "expiration": "1d" +} + +This creates an API key with the name `my_api_key` that +expires after one day. API key names must be globally unique. +An expiration date is optional and follows {ref}/common-options.html#time-units[{es} time unit format]. +When an expiration is not provided, the API key does not expire. + +[float] +[[view-api-keys]] +=== View and invalidate API keys +The *API Keys* UI lists your API keys, including the name, date created, +and expiration date. If an API key expires, its status changes from `Active` to `Expired`. + +If you have `manage_security` or `manage_api_key` permissions, +you can view the API keys of all users, and see which API key was +created by which user in which realm. +If you have only the `manage_own_api_key` permission, you see only a list of your own keys. + +You can invalidate API keys individually or in bulk. +Invalidated keys are deleted in batch after seven days. + +[role="screenshot"] +image:user/security/api-keys/images/api-key-invalidate.png["API Keys invalidate"] + +You cannot modify an API key. If you need additional privileges, +you must create a new key with the desired configuration and invalidate the old key. + + + + diff --git a/docs/user/security/index.asciidoc b/docs/user/security/index.asciidoc index 7b7e38d610843..f57d1bcd3bc2a 100644 --- a/docs/user/security/index.asciidoc +++ b/docs/user/security/index.asciidoc @@ -36,3 +36,5 @@ cause Kibana's authorization to behave unexpectedly. include::authorization/index.asciidoc[] include::authorization/kibana-privileges.asciidoc[] +include::api-keys/index.asciidoc[] + diff --git a/package.json b/package.json index 101cee27aa28f..01067ab02cb55 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,6 @@ "**/typescript": "3.5.3", "**/graphql-toolkit/lodash": "^4.17.13", "**/isomorphic-git/**/base64-js": "^1.2.1", - "**/babel-plugin-inline-react-svg/svgo/js-yaml": "^3.13.1", "**/image-diff/gm/debug": "^2.6.9" }, "workspaces": { @@ -107,10 +106,10 @@ "dependencies": { "@babel/core": "^7.5.5", "@babel/register": "^7.5.5", - "@elastic/charts": "^13.5.4", + "@elastic/charts": "^13.5.9", "@elastic/datemath": "5.0.2", "@elastic/ems-client": "1.0.5", - "@elastic/eui": "14.5.0", + "@elastic/eui": "14.7.0", "@elastic/filesaver": "1.1.2", "@elastic/good": "8.1.1-kibana2", "@elastic/numeral": "2.3.3", @@ -156,11 +155,11 @@ "custom-event-polyfill": "^0.3.0", "d3": "3.5.17", "d3-cloud": "1.2.5", - "del": "^4.1.1", + "del": "^5.1.0", "elasticsearch": "^16.4.0", "elasticsearch-browser": "^16.4.0", "encode-uri-query": "1.0.1", - "execa": "^1.0.0", + "execa": "^3.2.0", "expiry-js": "0.1.7", "file-loader": "4.2.0", "font-awesome": "4.7.0", @@ -199,8 +198,8 @@ "markdown-it": "^8.4.1", "mini-css-extract-plugin": "0.8.0", "minimatch": "^3.0.4", - "moment": "^2.20.1", - "moment-timezone": "^0.5.14", + "moment": "^2.24.0", + "moment-timezone": "^0.5.27", "mustache": "2.3.2", "ngreact": "0.5.1", "node-fetch": "1.7.3", @@ -233,12 +232,10 @@ "request": "^2.88.0", "reselect": "^3.0.1", "resize-observer-polyfill": "^1.5.0", - "rimraf": "2.7.1", "rison-node": "1.0.2", "rxjs": "^6.2.1", "script-loader": "0.7.2", "semver": "^5.5.0", - "stream-stream": "^1.2.6", "style-it": "^2.1.3", "style-loader": "0.23.1", "symbol-observable": "^1.2.0", @@ -248,8 +245,6 @@ "tinygradient": "0.4.3", "tinymath": "1.2.1", "topojson-client": "3.0.0", - "trunc-html": "1.1.2", - "trunc-text": "1.0.2", "tslib": "^1.9.3", "type-detect": "^4.0.8", "ui-select": "0.19.8", @@ -303,7 +298,6 @@ "@types/elasticsearch": "^5.0.33", "@types/enzyme": "^3.9.0", "@types/eslint": "^6.1.2", - "@types/execa": "^0.9.0", "@types/fetch-mock": "^7.3.1", "@types/getopts": "^2.0.1", "@types/glob": "^7.1.1", @@ -314,7 +308,6 @@ "@types/has-ansi": "^3.0.0", "@types/history": "^4.7.3", "@types/hoek": "^4.1.3", - "@types/humps": "^1.1.2", "@types/jest": "^24.0.18", "@types/joi": "^13.4.2", "@types/jquery": "^3.3.31", @@ -327,7 +320,7 @@ "@types/markdown-it": "^0.0.7", "@types/minimatch": "^2.0.29", "@types/mocha": "^5.2.7", - "@types/moment-timezone": "^0.5.8", + "@types/moment-timezone": "^0.5.12", "@types/mustache": "^0.8.31", "@types/node": "^10.12.27", "@types/opn": "^5.1.0", @@ -342,13 +335,13 @@ "@types/redux": "^3.6.31", "@types/redux-actions": "^2.2.1", "@types/request": "^2.48.2", - "@types/rimraf": "^2.0.2", "@types/selenium-webdriver": "^4.0.3", "@types/semver": "^5.5.0", "@types/sinon": "^7.0.13", "@types/strip-ansi": "^3.0.0", "@types/styled-components": "^3.0.2", "@types/supertest": "^2.0.5", + "@types/supertest-as-promised": "^2.0.38", "@types/type-detect": "^4.0.1", "@types/uuid": "^3.4.4", "@types/vinyl-fs": "^2.4.11", diff --git a/packages/elastic-datemath/package.json b/packages/elastic-datemath/package.json index 53c8459821147..57873d28d372d 100644 --- a/packages/elastic-datemath/package.json +++ b/packages/elastic-datemath/package.json @@ -14,12 +14,12 @@ "@babel/cli": "^7.5.5", "@babel/preset-env": "^7.5.5", "babel-plugin-add-module-exports": "^1.0.2", - "moment": "^2.13.0" + "moment": "^2.24.0" }, "dependencies": { "tslib": "^1.9.3" }, "peerDependencies": { - "moment": "^2.13.0" + "moment": "^2.24.0" } } diff --git a/packages/kbn-config-schema/package.json b/packages/kbn-config-schema/package.json index cd5ae010f998b..4880fb4ebfdee 100644 --- a/packages/kbn-config-schema/package.json +++ b/packages/kbn-config-schema/package.json @@ -14,7 +14,7 @@ }, "peerDependencies": { "joi": "^13.5.2", - "moment": "^2.20.1", + "moment": "^2.24.0", "type-detect": "^4.0.8" } } diff --git a/packages/kbn-config-schema/src/errors/validation_error.ts b/packages/kbn-config-schema/src/errors/validation_error.ts index 39aac67c208f5..d688d022da85c 100644 --- a/packages/kbn-config-schema/src/errors/validation_error.ts +++ b/packages/kbn-config-schema/src/errors/validation_error.ts @@ -20,17 +20,18 @@ import { SchemaError, SchemaTypeError, SchemaTypesError } from '.'; export class ValidationError extends SchemaError { - public static extractMessage(error: SchemaTypeError, namespace?: string) { + private static extractMessage(error: SchemaTypeError, namespace?: string, level?: number) { const path = typeof namespace === 'string' ? [namespace, ...error.path] : error.path; let message = error.message; if (error instanceof SchemaTypesError) { + const indentLevel = level || 0; const childErrorMessages = error.errors.map(childError => - ValidationError.extractMessage(childError, namespace) + ValidationError.extractMessage(childError, namespace, indentLevel + 1) ); message = `${message}\n${childErrorMessages - .map(childErrorMessage => `- ${childErrorMessage}`) + .map(childErrorMessage => `${' '.repeat(indentLevel)}- ${childErrorMessage}`) .join('\n')}`; } diff --git a/packages/kbn-config-schema/src/types/map_of_type.test.ts b/packages/kbn-config-schema/src/types/map_of_type.test.ts index 1b72d39fcec26..6b9b700efdc3c 100644 --- a/packages/kbn-config-schema/src/types/map_of_type.test.ts +++ b/packages/kbn-config-schema/src/types/map_of_type.test.ts @@ -108,3 +108,23 @@ test('object within mapOf', () => { expect(type.validate(value)).toEqual(expected); }); + +test('error preserves full path', () => { + const type = schema.object({ + grandParentKey: schema.object({ + parentKey: schema.mapOf(schema.string({ minLength: 2 }), schema.number()), + }), + }); + + expect(() => + type.validate({ grandParentKey: { parentKey: { a: 'some-value' } } }) + ).toThrowErrorMatchingInlineSnapshot( + `"[grandParentKey.parentKey.key(\\"a\\")]: value is [a] but it must have a minimum length of [2]."` + ); + + expect(() => + type.validate({ grandParentKey: { parentKey: { ab: 'some-value' } } }) + ).toThrowErrorMatchingInlineSnapshot( + `"[grandParentKey.parentKey.ab]: expected value of type [number] but got [string]"` + ); +}); diff --git a/packages/kbn-config-schema/src/types/map_type.ts b/packages/kbn-config-schema/src/types/map_type.ts index 3acf14a69125e..c637eccb79571 100644 --- a/packages/kbn-config-schema/src/types/map_type.ts +++ b/packages/kbn-config-schema/src/types/map_type.ts @@ -50,7 +50,7 @@ export class MapOfType extends Type> { return `expected value of type [Map] or [object] but got [${typeDetect(value)}]`; case 'map.key': case 'map.value': - const childPathWithIndex = reason.path.slice(); + const childPathWithIndex = path.slice(); childPathWithIndex.splice( path.length, 0, diff --git a/packages/kbn-config-schema/src/types/one_of_type.test.ts b/packages/kbn-config-schema/src/types/one_of_type.test.ts index 72119e761590b..c84ae49df7aef 100644 --- a/packages/kbn-config-schema/src/types/one_of_type.test.ts +++ b/packages/kbn-config-schema/src/types/one_of_type.test.ts @@ -125,3 +125,20 @@ test('fails if not matching literal', () => { expect(() => type.validate('bar')).toThrowErrorMatchingSnapshot(); }); + +test('fails if nested union type fail', () => { + const type = schema.oneOf([ + schema.oneOf([schema.boolean()]), + schema.oneOf([schema.oneOf([schema.object({}), schema.number()])]), + ]); + + expect(() => type.validate('aaa')).toThrowErrorMatchingInlineSnapshot(` +"types that failed validation: +- [0]: types that failed validation: + - [0]: expected value of type [boolean] but got [string] +- [1]: types that failed validation: + - [0]: types that failed validation: + - [0]: expected a plain object value, but found [string] instead. + - [1]: expected value of type [number] but got [string]" +`); +}); diff --git a/packages/kbn-config-schema/src/types/record_of_type.test.ts b/packages/kbn-config-schema/src/types/record_of_type.test.ts index 036e34213411f..2172160e8d181 100644 --- a/packages/kbn-config-schema/src/types/record_of_type.test.ts +++ b/packages/kbn-config-schema/src/types/record_of_type.test.ts @@ -120,3 +120,23 @@ test('object within recordOf', () => { expect(type.validate(value)).toEqual({ foo: { bar: 123 } }); }); + +test('error preserves full path', () => { + const type = schema.object({ + grandParentKey: schema.object({ + parentKey: schema.recordOf(schema.string({ minLength: 2 }), schema.number()), + }), + }); + + expect(() => + type.validate({ grandParentKey: { parentKey: { a: 'some-value' } } }) + ).toThrowErrorMatchingInlineSnapshot( + `"[grandParentKey.parentKey.key(\\"a\\")]: value is [a] but it must have a minimum length of [2]."` + ); + + expect(() => + type.validate({ grandParentKey: { parentKey: { ab: 'some-value' } } }) + ).toThrowErrorMatchingInlineSnapshot( + `"[grandParentKey.parentKey.ab]: expected value of type [number] but got [string]"` + ); +}); diff --git a/packages/kbn-config-schema/src/types/record_type.ts b/packages/kbn-config-schema/src/types/record_type.ts index cefb0a7921f4d..82e585f685c56 100644 --- a/packages/kbn-config-schema/src/types/record_type.ts +++ b/packages/kbn-config-schema/src/types/record_type.ts @@ -42,7 +42,7 @@ export class RecordOfType extends Type> { return `expected value of type [object] but got [${typeDetect(value)}]`; case 'record.key': case 'record.value': - const childPathWithIndex = reason.path.slice(); + const childPathWithIndex = path.slice(); childPathWithIndex.splice( path.length, 0, diff --git a/packages/kbn-config-schema/src/types/union_type.ts b/packages/kbn-config-schema/src/types/union_type.ts index e6efb4afb66aa..f4de829204e80 100644 --- a/packages/kbn-config-schema/src/types/union_type.ts +++ b/packages/kbn-config-schema/src/types/union_type.ts @@ -41,7 +41,9 @@ export class UnionType>, T> extends Type { const childPathWithIndex = e.path.slice(); childPathWithIndex.splice(path.length, 0, index.toString()); - return new SchemaTypeError(e.message, childPathWithIndex); + return e instanceof SchemaTypesError + ? new SchemaTypesError(e.message, childPathWithIndex, e.errors) + : new SchemaTypeError(e.message, childPathWithIndex); }) ); } diff --git a/packages/kbn-dev-utils/package.json b/packages/kbn-dev-utils/package.json index 5c64be294f707..a5789a0a105b3 100644 --- a/packages/kbn-dev-utils/package.json +++ b/packages/kbn-dev-utils/package.json @@ -12,10 +12,10 @@ "dependencies": { "chalk": "^2.4.2", "dedent": "^0.7.0", - "execa": "^1.0.0", + "execa": "^3.2.0", "exit-hook": "^2.2.0", "getopts": "^2.2.5", - "moment": "^2.20.1", + "moment": "^2.24.0", "rxjs": "^6.2.1", "tree-kill": "^1.2.1", "tslib": "^1.9.3" diff --git a/packages/kbn-dev-utils/src/proc_runner/proc.ts b/packages/kbn-dev-utils/src/proc_runner/proc.ts index f29fb5f4b17f6..dab69de47af61 100644 --- a/packages/kbn-dev-utils/src/proc_runner/proc.ts +++ b/packages/kbn-dev-utils/src/proc_runner/proc.ts @@ -87,6 +87,7 @@ export function startProc(name: string, options: ProcOptions, log: ToolingLog) { cwd, env, stdio: ['pipe', 'pipe', 'pipe'], + preferLocal: true, }); if (stdin) { diff --git a/packages/kbn-es-query/package.json b/packages/kbn-es-query/package.json index 5ef1b6e125413..2cd2a8f53d2ee 100644 --- a/packages/kbn-es-query/package.json +++ b/packages/kbn-es-query/package.json @@ -12,7 +12,7 @@ }, "dependencies": { "lodash": "npm:@elastic/lodash@3.10.1-kibana3", - "moment-timezone": "^0.5.14", + "moment-timezone": "^0.5.27", "@kbn/i18n": "1.0.0" }, "devDependencies": { @@ -21,7 +21,7 @@ "@kbn/babel-preset": "1.0.0", "@kbn/dev-utils": "1.0.0", "@kbn/expect": "1.0.0", - "del": "^4.1.1", + "del": "^5.1.0", "getopts": "^2.2.4", "supports-color": "^7.0.0" } diff --git a/packages/kbn-es/package.json b/packages/kbn-es/package.json index 5280c671450fa..cb501dab3ddb7 100644 --- a/packages/kbn-es/package.json +++ b/packages/kbn-es/package.json @@ -10,8 +10,8 @@ "abort-controller": "^2.0.3", "chalk": "^2.4.2", "dedent": "^0.7.0", - "del": "^4.1.1", - "execa": "^1.0.0", + "del": "^5.1.0", + "execa": "^3.2.0", "getopts": "^2.2.4", "glob": "^7.1.2", "node-fetch": "^2.6.0", diff --git a/packages/kbn-expect/expect.js b/packages/kbn-expect/expect.js index 8dc8af4cab894..bc75d19d2ab1f 100644 --- a/packages/kbn-expect/expect.js +++ b/packages/kbn-expect/expect.js @@ -98,6 +98,9 @@ Assertion.prototype.assert = function (truth, msg, error, expected) { if (!ok) { err = new Error(msg.call(this)); + if (this.customMsg) { + err.message = this.customMsg; + } if (arguments.length > 3) { err.actual = this.obj; err.expected = expected; @@ -217,7 +220,10 @@ Assertion.prototype.empty = function () { */ Assertion.prototype.be = -Assertion.prototype.equal = function (obj) { +Assertion.prototype.equal = function (obj, msg) { + if (typeof(msg) === 'string') { + this.customMsg = msg; + } this.assert( obj === this.obj , function(){ return 'expected ' + i(this.obj) + ' to equal ' + i(obj) } @@ -231,7 +237,10 @@ Assertion.prototype.equal = function (obj) { * @api public */ -Assertion.prototype.eql = function (obj) { +Assertion.prototype.eql = function (obj, msg) { + if (typeof(msg) === 'string') { + this.customMsg = msg; + } this.assert( expect.eql(this.obj, obj) , function(){ return 'expected ' + i(this.obj) + ' to sort of equal ' + i(obj) } @@ -248,7 +257,10 @@ Assertion.prototype.eql = function (obj) { * @api public */ -Assertion.prototype.within = function (start, finish) { +Assertion.prototype.within = function (start, finish, msg) { + if (typeof(msg) === 'string') { + this.customMsg = msg; + } var range = start + '..' + finish; this.assert( this.obj >= start && this.obj <= finish @@ -298,7 +310,10 @@ Assertion.prototype.an = function (type) { */ Assertion.prototype.greaterThan = -Assertion.prototype.above = function (n) { +Assertion.prototype.above = function (n, msg) { + if (typeof(msg) === 'string') { + this.customMsg = msg; + } this.assert( this.obj > n , function(){ return 'expected ' + i(this.obj) + ' to be above ' + n } @@ -314,7 +329,10 @@ Assertion.prototype.above = function (n) { */ Assertion.prototype.lessThan = -Assertion.prototype.below = function (n) { +Assertion.prototype.below = function (n, msg) { + if (typeof(msg) === 'string') { + this.customMsg = msg; + } this.assert( this.obj < n , function(){ return 'expected ' + i(this.obj) + ' to be below ' + n } @@ -329,7 +347,10 @@ Assertion.prototype.below = function (n) { * @api public */ -Assertion.prototype.match = function (regexp) { +Assertion.prototype.match = function (regexp, msg) { + if (typeof(msg) === 'string') { + this.customMsg = msg; + } this.assert( regexp.exec(this.obj) , function(){ return 'expected ' + i(this.obj) + ' to match ' + regexp } @@ -344,7 +365,10 @@ Assertion.prototype.match = function (regexp) { * @api public */ -Assertion.prototype.length = function (n) { +Assertion.prototype.length = function (n, msg) { + if (typeof(msg) === 'string') { + this.customMsg = msg; + } expect(this.obj).to.have.property('length'); var len = this.obj.length; this.assert( @@ -410,7 +434,10 @@ Assertion.prototype.property = function (name, val) { */ Assertion.prototype.string = -Assertion.prototype.contain = function (obj) { +Assertion.prototype.contain = function (obj, msg) { + if (typeof(msg) === 'string') { + this.customMsg = msg; + } if ('string' == typeof this.obj) { this.assert( ~this.obj.indexOf(obj) diff --git a/packages/kbn-expect/expect.js.d.ts b/packages/kbn-expect/expect.js.d.ts index 2062dea686500..b957a1f9ab109 100644 --- a/packages/kbn-expect/expect.js.d.ts +++ b/packages/kbn-expect/expect.js.d.ts @@ -59,12 +59,12 @@ interface Assertion { /** * Checks if the obj exactly equals another. */ - equal(obj: any): Assertion; + equal(obj: any, msg?: string): Assertion; /** * Checks if the obj sortof equals another. */ - eql(obj: any): Assertion; + eql(obj: any, msg?: string): Assertion; /** * Assert within start to finish (inclusive). @@ -72,7 +72,7 @@ interface Assertion { * @param start * @param finish */ - within(start: number, finish: number): Assertion; + within(start: number, finish: number, msg?: string): Assertion; /** * Assert typeof. @@ -87,36 +87,36 @@ interface Assertion { /** * Assert numeric value above n. */ - greaterThan(n: number): Assertion; + greaterThan(n: number, msg?: string): Assertion; /** * Assert numeric value above n. */ - above(n: number): Assertion; + above(n: number, msg?: string): Assertion; /** * Assert numeric value below n. */ - lessThan(n: number): Assertion; + lessThan(n: number, msg?: string): Assertion; /** * Assert numeric value below n. */ - below(n: number): Assertion; + below(n: number, msg?: string): Assertion; /** * Assert string value matches regexp. * * @param regexp */ - match(regexp: RegExp): Assertion; + match(regexp: RegExp, msg?: string): Assertion; /** * Assert property "length" exists and has value of n. * * @param n */ - length(n: number): Assertion; + length(n: number, msg?: string): Assertion; /** * Assert property name exists, with optional val. @@ -129,14 +129,14 @@ interface Assertion { /** * Assert that string contains str. */ - contain(str: string): Assertion; - string(str: string): Assertion; + contain(str: string, msg?: string): Assertion; + string(str: string, msg?: string): Assertion; /** * Assert that the array contains obj. */ - contain(obj: any): Assertion; - string(obj: any): Assertion; + contain(obj: any, msg?: string): Assertion; + string(obj: any, msg?: string): Assertion; /** * Assert exact keys or inclusion of keys by using the `.own` modifier. diff --git a/packages/kbn-i18n/package.json b/packages/kbn-i18n/package.json index dff8a3f2f4b78..8a88626bffbe8 100644 --- a/packages/kbn-i18n/package.json +++ b/packages/kbn-i18n/package.json @@ -18,7 +18,7 @@ "@kbn/dev-utils": "1.0.0", "@types/intl-relativeformat": "^2.1.0", "@types/react-intl": "^2.3.15", - "del": "^4.1.1", + "del": "^5.1.0", "getopts": "^2.2.4", "supports-color": "^7.0.0", "typescript": "3.5.3" diff --git a/packages/kbn-interpreter/package.json b/packages/kbn-interpreter/package.json index f2aa2e1ced9cd..27ef70d871856 100644 --- a/packages/kbn-interpreter/package.json +++ b/packages/kbn-interpreter/package.json @@ -24,7 +24,7 @@ "babel-loader": "^8.0.6", "copy-webpack-plugin": "^5.0.4", "css-loader": "2.1.1", - "del": "^4.1.1", + "del": "^5.1.0", "getopts": "^2.2.4", "pegjs": "0.10.0", "sass-loader": "^7.3.1", diff --git a/packages/kbn-plugin-generator/package.json b/packages/kbn-plugin-generator/package.json index 74ccbd8e758b5..ac98a0e675fb1 100644 --- a/packages/kbn-plugin-generator/package.json +++ b/packages/kbn-plugin-generator/package.json @@ -6,7 +6,7 @@ "dependencies": { "chalk": "^2.4.2", "dedent": "^0.7.0", - "execa": "^1.0.0", + "execa": "^3.2.0", "getopts": "^2.2.4", "lodash.camelcase": "^4.3.0", "lodash.kebabcase": "^4.1.1", diff --git a/packages/kbn-plugin-generator/sao_template/template/public/app.js b/packages/kbn-plugin-generator/sao_template/template/public/app.js index 031878d70f9f3..37a7c37e916a0 100755 --- a/packages/kbn-plugin-generator/sao_template/template/public/app.js +++ b/packages/kbn-plugin-generator/sao_template/template/public/app.js @@ -6,7 +6,6 @@ import { render, unmountComponentAtNode } from 'react-dom'; import { I18nProvider } from '@kbn/i18n/react'; <%_ } _%> -import 'ui/autoload/styles'; import { Main } from './components/main'; const app = uiModules.get('apps/<%= camelCase(name) %>'); diff --git a/packages/kbn-plugin-helpers/package.json b/packages/kbn-plugin-helpers/package.json index 215963c0769bb..68af0aa791c8e 100644 --- a/packages/kbn-plugin-helpers/package.json +++ b/packages/kbn-plugin-helpers/package.json @@ -16,8 +16,8 @@ "@babel/core": "^7.5.5", "argv-split": "^2.0.1", "commander": "^3.0.0", - "del": "^4.1.1", - "execa": "^1.0.0", + "del": "^5.1.0", + "execa": "^3.2.0", "globby": "^8.0.1", "gulp-babel": "^8.0.0", "gulp-rename": "1.4.0", diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index 3dd27e7ef3800..2a8c22ed29a79 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -94,7 +94,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _cli__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "run", function() { return _cli__WEBPACK_IMPORTED_MODULE_0__["run"]; }); -/* harmony import */ var _production__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(404); +/* harmony import */ var _production__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(483); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildProductionProjects", function() { return _production__WEBPACK_IMPORTED_MODULE_1__["buildProductionProjects"]; }); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "prepareExternalProjectDependencies", function() { return _production__WEBPACK_IMPORTED_MODULE_1__["prepareExternalProjectDependencies"]; }); @@ -105,10 +105,10 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _utils_project__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(54); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "Project", function() { return _utils_project__WEBPACK_IMPORTED_MODULE_3__["Project"]; }); -/* harmony import */ var _utils_workspaces__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(163); +/* harmony import */ var _utils_workspaces__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(171); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "copyWorkspacePackages", function() { return _utils_workspaces__WEBPACK_IMPORTED_MODULE_4__["copyWorkspacePackages"]; }); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(164); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(172); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "getProjectPaths", function() { return _config__WEBPACK_IMPORTED_MODULE_5__["getProjectPaths"]; }); /* @@ -152,7 +152,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(16); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_3__); /* harmony import */ var _commands__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(17); -/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(394); +/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(474); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(34); /* * Licensed to Elasticsearch B.V. under one or more contributor @@ -2502,9 +2502,9 @@ module.exports = require("path"); __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "commands", function() { return commands; }); /* harmony import */ var _bootstrap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(18); -/* harmony import */ var _clean__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(165); -/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(192); -/* harmony import */ var _watch__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(193); +/* harmony import */ var _clean__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(173); +/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(272); +/* harmony import */ var _watch__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(273); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -4415,7 +4415,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var util__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(util__WEBPACK_IMPORTED_MODULE_2__); /* harmony import */ var _errors__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(53); /* harmony import */ var _project__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(54); -/* harmony import */ var _workspaces__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(163); +/* harmony import */ var _workspaces__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(171); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -19082,9 +19082,9 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(chalk__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var execa__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(122); /* harmony import */ var execa__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(execa__WEBPACK_IMPORTED_MODULE_1__); -/* harmony import */ var log_symbols__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(150); +/* harmony import */ var log_symbols__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(158); /* harmony import */ var log_symbols__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(log_symbols__WEBPACK_IMPORTED_MODULE_2__); -/* harmony import */ var strong_log_transformer__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(155); +/* harmony import */ var strong_log_transformer__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(163); /* harmony import */ var strong_log_transformer__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(strong_log_transformer__WEBPACK_IMPORTED_MODULE_3__); function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } @@ -19124,7 +19124,8 @@ function generateColors() { function spawn(command, args, opts) { return execa__WEBPACK_IMPORTED_MODULE_1___default()(command, args, _objectSpread({ - stdio: 'inherit' + stdio: 'inherit', + preferLocal: true }, opts)); } const nextColor = generateColors(); @@ -19132,7 +19133,8 @@ function spawnStreaming(command, args, opts, { prefix }) { const spawned = execa__WEBPACK_IMPORTED_MODULE_1___default()(command, args, _objectSpread({ - stdio: ['ignore', 'pipe', 'pipe'] + stdio: ['ignore', 'pipe', 'pipe'], + preferLocal: true }, opts)); const color = nextColor(); const prefixedStdout = strong_log_transformer__WEBPACK_IMPORTED_MODULE_3___default()({ @@ -19156,363 +19158,258 @@ function spawnStreaming(command, args, opts, { const path = __webpack_require__(16); const childProcess = __webpack_require__(123); const crossSpawn = __webpack_require__(124); -const stripEof = __webpack_require__(139); -const npmRunPath = __webpack_require__(140); -const isStream = __webpack_require__(142); -const _getStream = __webpack_require__(143); -const pFinally = __webpack_require__(147); -const onExit = __webpack_require__(110); -const errname = __webpack_require__(148); -const stdio = __webpack_require__(149); - -const TEN_MEGABYTES = 1000 * 1000 * 10; - -function handleArgs(cmd, args, opts) { - let parsed; - - opts = Object.assign({ - extendEnv: true, - env: {} - }, opts); - - if (opts.extendEnv) { - opts.env = Object.assign({}, process.env, opts.env); +const stripFinalNewline = __webpack_require__(137); +const npmRunPath = __webpack_require__(138); +const onetime = __webpack_require__(139); +const makeError = __webpack_require__(141); +const normalizeStdio = __webpack_require__(146); +const {spawnedKill, spawnedCancel, setupTimeout, setExitHandler} = __webpack_require__(147); +const {handleInput, getSpawnedResult, makeAllStream, validateInputSync} = __webpack_require__(149); +const {mergePromise, getSpawnedPromise} = __webpack_require__(156); +const {joinCommand, parseCommand} = __webpack_require__(157); + +const DEFAULT_MAX_BUFFER = 1000 * 1000 * 100; + +const getEnv = ({env: envOption, extendEnv, preferLocal, localDir, execPath}) => { + const env = extendEnv ? {...process.env, ...envOption} : envOption; + + if (preferLocal) { + return npmRunPath.env({env, cwd: localDir, execPath}); } - if (opts.__winShell === true) { - delete opts.__winShell; - parsed = { - command: cmd, - args, - options: opts, - file: cmd, - original: { - cmd, - args - } - }; - } else { - parsed = crossSpawn._parse(cmd, args, opts); - } + return env; +}; - opts = Object.assign({ - maxBuffer: TEN_MEGABYTES, +const handleArgs = (file, args, options = {}) => { + const parsed = crossSpawn._parse(file, args, options); + file = parsed.command; + args = parsed.args; + options = parsed.options; + + options = { + maxBuffer: DEFAULT_MAX_BUFFER, buffer: true, - stripEof: true, - preferLocal: true, - localDir: parsed.options.cwd || process.cwd(), + stripFinalNewline: true, + extendEnv: true, + preferLocal: false, + localDir: options.cwd || process.cwd(), + execPath: process.execPath, encoding: 'utf8', reject: true, - cleanup: true - }, parsed.options); - - opts.stdio = stdio(opts); - - if (opts.preferLocal) { - opts.env = npmRunPath.env(Object.assign({}, opts, {cwd: opts.localDir})); - } - - if (opts.detached) { - // #115 - opts.cleanup = false; - } - - if (process.platform === 'win32' && path.basename(parsed.command) === 'cmd.exe') { - // #116 - parsed.args.unshift('/q'); - } - - return { - cmd: parsed.command, - args: parsed.args, - opts, - parsed + cleanup: true, + all: false, + ...options, + windowsHide: true }; -} - -function handleInput(spawned, input) { - if (input === null || input === undefined) { - return; - } - - if (isStream(input)) { - input.pipe(spawned.stdin); - } else { - spawned.stdin.end(input); - } -} - -function handleOutput(opts, val) { - if (val && opts.stripEof) { - val = stripEof(val); - } - - return val; -} - -function handleShell(fn, cmd, opts) { - let file = '/bin/sh'; - let args = ['-c', cmd]; - - opts = Object.assign({}, opts); - - if (process.platform === 'win32') { - opts.__winShell = true; - file = process.env.comspec || 'cmd.exe'; - args = ['/s', '/c', `"${cmd}"`]; - opts.windowsVerbatimArguments = true; - } - - if (opts.shell) { - file = opts.shell; - delete opts.shell; - } - - return fn(file, args, opts); -} -function getStream(process, stream, {encoding, buffer, maxBuffer}) { - if (!process[stream]) { - return null; - } + options.env = getEnv(options); - let ret; + options.stdio = normalizeStdio(options); - if (!buffer) { - // TODO: Use `ret = util.promisify(stream.finished)(process[stream]);` when targeting Node.js 10 - ret = new Promise((resolve, reject) => { - process[stream] - .once('end', resolve) - .once('error', reject); - }); - } else if (encoding) { - ret = _getStream(process[stream], { - encoding, - maxBuffer - }); - } else { - ret = _getStream.buffer(process[stream], {maxBuffer}); + if (process.platform === 'win32' && path.basename(file, '.exe') === 'cmd') { + // #116 + args.unshift('/q'); } - return ret.catch(err => { - err.stream = stream; - err.message = `${stream} ${err.message}`; - throw err; - }); -} - -function makeError(result, options) { - const {stdout, stderr} = result; - - let err = result.error; - const {code, signal} = result; - - const {parsed, joinedCmd} = options; - const timedOut = options.timedOut || false; - - if (!err) { - let output = ''; - - if (Array.isArray(parsed.opts.stdio)) { - if (parsed.opts.stdio[2] !== 'inherit') { - output += output.length > 0 ? stderr : `\n${stderr}`; - } - - if (parsed.opts.stdio[1] !== 'inherit') { - output += `\n${stdout}`; - } - } else if (parsed.opts.stdio !== 'inherit') { - output = `\n${stderr}${stdout}`; - } + return {file, args, options, parsed}; +}; - err = new Error(`Command failed: ${joinedCmd}${output}`); - err.code = code < 0 ? errname(code) : code; +const handleOutput = (options, value, error) => { + if (typeof value !== 'string' && !Buffer.isBuffer(value)) { + // When `execa.sync()` errors, we normalize it to '' to mimic `execa()` + return error === undefined ? undefined : ''; } - err.stdout = stdout; - err.stderr = stderr; - err.failed = true; - err.signal = signal || null; - err.cmd = joinedCmd; - err.timedOut = timedOut; - - return err; -} - -function joinCmd(cmd, args) { - let joinedCmd = cmd; - - if (Array.isArray(args) && args.length > 0) { - joinedCmd += ' ' + args.join(' '); + if (options.stripFinalNewline) { + return stripFinalNewline(value); } - return joinedCmd; -} + return value; +}; -module.exports = (cmd, args, opts) => { - const parsed = handleArgs(cmd, args, opts); - const {encoding, buffer, maxBuffer} = parsed.opts; - const joinedCmd = joinCmd(cmd, args); +const execa = (file, args, options) => { + const parsed = handleArgs(file, args, options); + const command = joinCommand(file, args); let spawned; try { - spawned = childProcess.spawn(parsed.cmd, parsed.args, parsed.opts); - } catch (err) { - return Promise.reject(err); - } - - let removeExitHandler; - if (parsed.opts.cleanup) { - removeExitHandler = onExit(() => { - spawned.kill(); - }); - } - - let timeoutId = null; - let timedOut = false; - - const cleanup = () => { - if (timeoutId) { - clearTimeout(timeoutId); - timeoutId = null; - } - - if (removeExitHandler) { - removeExitHandler(); - } - }; - - if (parsed.opts.timeout > 0) { - timeoutId = setTimeout(() => { - timeoutId = null; - timedOut = true; - spawned.kill(parsed.opts.killSignal); - }, parsed.opts.timeout); - } - - const processDone = new Promise(resolve => { - spawned.on('exit', (code, signal) => { - cleanup(); - resolve({code, signal}); - }); - - spawned.on('error', err => { - cleanup(); - resolve({error: err}); - }); - - if (spawned.stdin) { - spawned.stdin.on('error', err => { - cleanup(); - resolve({error: err}); - }); - } - }); - - function destroy() { - if (spawned.stdout) { - spawned.stdout.destroy(); - } - - if (spawned.stderr) { - spawned.stderr.destroy(); - } + spawned = childProcess.spawn(parsed.file, parsed.args, parsed.options); + } catch (error) { + // Ensure the returned error is always both a promise and a child process + const dummySpawned = new childProcess.ChildProcess(); + const errorPromise = Promise.reject(makeError({ + error, + stdout: '', + stderr: '', + all: '', + command, + parsed, + timedOut: false, + isCanceled: false, + killed: false + })); + return mergePromise(dummySpawned, errorPromise); } - const handlePromise = () => pFinally(Promise.all([ - processDone, - getStream(spawned, 'stdout', {encoding, buffer, maxBuffer}), - getStream(spawned, 'stderr', {encoding, buffer, maxBuffer}) - ]).then(arr => { - const result = arr[0]; - result.stdout = arr[1]; - result.stderr = arr[2]; - - if (result.error || result.code !== 0 || result.signal !== null) { - const err = makeError(result, { - joinedCmd, + const spawnedPromise = getSpawnedPromise(spawned); + const timedPromise = setupTimeout(spawned, parsed.options, spawnedPromise); + const processDone = setExitHandler(spawned, parsed.options, timedPromise); + + const context = {isCanceled: false}; + + spawned.kill = spawnedKill.bind(null, spawned.kill.bind(spawned)); + spawned.cancel = spawnedCancel.bind(null, spawned, context); + + const handlePromise = async () => { + const [{error, exitCode, signal, timedOut}, stdoutResult, stderrResult, allResult] = await getSpawnedResult(spawned, parsed.options, processDone); + const stdout = handleOutput(parsed.options, stdoutResult); + const stderr = handleOutput(parsed.options, stderrResult); + const all = handleOutput(parsed.options, allResult); + + if (error || exitCode !== 0 || signal !== null) { + const returnedError = makeError({ + error, + exitCode, + signal, + stdout, + stderr, + all, + command, parsed, - timedOut + timedOut, + isCanceled: context.isCanceled, + killed: spawned.killed }); - // TODO: missing some timeout logic for killed - // https://github.com/nodejs/node/blob/master/lib/child_process.js#L203 - // err.killed = spawned.killed || killed; - err.killed = err.killed || spawned.killed; - - if (!parsed.opts.reject) { - return err; + if (!parsed.options.reject) { + return returnedError; } - throw err; + throw returnedError; } return { - stdout: handleOutput(parsed.opts, result.stdout), - stderr: handleOutput(parsed.opts, result.stderr), - code: 0, + command, + exitCode: 0, + stdout, + stderr, + all, failed: false, - killed: false, - signal: null, - cmd: joinedCmd, - timedOut: false + timedOut: false, + isCanceled: false, + killed: false }; - }), destroy); + }; + + const handlePromiseOnce = onetime(handlePromise); crossSpawn._enoent.hookChildProcess(spawned, parsed.parsed); - handleInput(spawned, parsed.opts.input); + handleInput(spawned, parsed.options.input); - spawned.then = (onfulfilled, onrejected) => handlePromise().then(onfulfilled, onrejected); - spawned.catch = onrejected => handlePromise().catch(onrejected); + spawned.all = makeAllStream(spawned, parsed.options); - return spawned; + return mergePromise(spawned, handlePromiseOnce); }; -// TODO: set `stderr: 'ignore'` when that option is implemented -module.exports.stdout = (...args) => module.exports(...args).then(x => x.stdout); +module.exports = execa; -// TODO: set `stdout: 'ignore'` when that option is implemented -module.exports.stderr = (...args) => module.exports(...args).then(x => x.stderr); +module.exports.sync = (file, args, options) => { + const parsed = handleArgs(file, args, options); + const command = joinCommand(file, args); -module.exports.shell = (cmd, opts) => handleShell(module.exports, cmd, opts); + validateInputSync(parsed.options); -module.exports.sync = (cmd, args, opts) => { - const parsed = handleArgs(cmd, args, opts); - const joinedCmd = joinCmd(cmd, args); - - if (isStream(parsed.opts.input)) { - throw new TypeError('The `input` option cannot be a stream in sync mode'); + let result; + try { + result = childProcess.spawnSync(parsed.file, parsed.args, parsed.options); + } catch (error) { + throw makeError({ + error, + stdout: '', + stderr: '', + all: '', + command, + parsed, + timedOut: false, + isCanceled: false, + killed: false + }); } - const result = childProcess.spawnSync(parsed.cmd, parsed.args, parsed.opts); - result.code = result.status; + const stdout = handleOutput(parsed.options, result.stdout, result.error); + const stderr = handleOutput(parsed.options, result.stderr, result.error); if (result.error || result.status !== 0 || result.signal !== null) { - const err = makeError(result, { - joinedCmd, - parsed + const error = makeError({ + stdout, + stderr, + error: result.error, + signal: result.signal, + exitCode: result.status, + command, + parsed, + timedOut: result.error && result.error.code === 'ETIMEDOUT', + isCanceled: false, + killed: result.signal !== null }); - if (!parsed.opts.reject) { - return err; + if (!parsed.options.reject) { + return error; } - throw err; + throw error; } return { - stdout: handleOutput(parsed.opts, result.stdout), - stderr: handleOutput(parsed.opts, result.stderr), - code: 0, + command, + exitCode: 0, + stdout, + stderr, failed: false, - signal: null, - cmd: joinedCmd, - timedOut: false + timedOut: false, + isCanceled: false, + killed: false }; }; -module.exports.shellSync = (cmd, opts) => handleShell(module.exports.sync, cmd, opts); +module.exports.command = (command, options) => { + const [file, ...args] = parseCommand(command); + return execa(file, args, options); +}; + +module.exports.commandSync = (command, options) => { + const [file, ...args] = parseCommand(command); + return execa.sync(file, args, options); +}; + +module.exports.node = (scriptPath, args, options = {}) => { + if (args && !Array.isArray(args) && typeof args === 'object') { + options = args; + args = []; + } + + const stdio = normalizeStdio.node(options); + + const {nodePath = process.execPath, nodeOptions = process.execArgv} = options; + + return execa( + nodePath, + [ + ...nodeOptions, + scriptPath, + ...(Array.isArray(args) ? args : []) + ], + { + ...options, + stdin: undefined, + stdout: undefined, + stderr: undefined, + stdio, + shell: false + } + ); +}; /***/ }), @@ -19530,7 +19427,7 @@ module.exports = require("child_process"); const cp = __webpack_require__(123); const parse = __webpack_require__(125); -const enoent = __webpack_require__(138); +const enoent = __webpack_require__(136); function spawn(command, args, options) { // Parse the arguments @@ -19575,19 +19472,14 @@ module.exports._enoent = enoent; const path = __webpack_require__(16); -const niceTry = __webpack_require__(126); -const resolveCommand = __webpack_require__(127); -const escape = __webpack_require__(133); -const readShebang = __webpack_require__(134); -const semver = __webpack_require__(137); +const resolveCommand = __webpack_require__(126); +const escape = __webpack_require__(132); +const readShebang = __webpack_require__(133); const isWin = process.platform === 'win32'; const isExecutableRegExp = /\.(?:com|exe)$/i; const isCmdShimRegExp = /node_modules[\\/].bin[\\/][^\\/]+\.cmd$/i; -// `options.shell` is supported in Node ^4.8.0, ^5.7.0 and >= 6.0.0 -const supportsShellOption = niceTry(() => semver.satisfies(process.version, '^4.8.0 || ^5.7.0 || >= 6.0.0', true)) || false; - function detectShebang(parsed) { parsed.file = resolveCommand(parsed); @@ -19641,35 +19533,6 @@ function parseNonShell(parsed) { return parsed; } -function parseShell(parsed) { - // If node supports the shell option, there's no need to mimic its behavior - if (supportsShellOption) { - return parsed; - } - - // Mimic node shell option - // See https://github.com/nodejs/node/blob/b9f6a2dc059a1062776133f3d4fd848c4da7d150/lib/child_process.js#L335 - const shellCommand = [parsed.command].concat(parsed.args).join(' '); - - if (isWin) { - parsed.command = typeof parsed.options.shell === 'string' ? parsed.options.shell : process.env.comspec || 'cmd.exe'; - parsed.args = ['/d', '/s', '/c', `"${shellCommand}"`]; - parsed.options.windowsVerbatimArguments = true; // Tell node's spawn that the arguments are already escaped - } else { - if (typeof parsed.options.shell === 'string') { - parsed.command = parsed.options.shell; - } else if (process.platform === 'android') { - parsed.command = '/system/bin/sh'; - } else { - parsed.command = '/bin/sh'; - } - - parsed.args = ['-c', shellCommand]; - } - - return parsed; -} - function parse(command, args, options) { // Normalize arguments, similar to nodejs if (args && !Array.isArray(args)) { @@ -19693,7 +19556,7 @@ function parse(command, args, options) { }; // Delegate further parsing to shell or non-shell - return options.shell ? parseShell(parsed) : parseNonShell(parsed); + return options.shell ? parsed : parseNonShell(parsed); } module.exports = parse; @@ -19706,36 +19569,19 @@ module.exports = parse; "use strict"; -/** - * Tries to execute a function and discards any error that occurs. - * @param {Function} fn - Function that might or might not throw an error. - * @returns {?*} Return-value of the function when no error occurred. - */ -module.exports = function(fn) { - - try { return fn() } - catch (e) {} - -} - -/***/ }), -/* 127 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - const path = __webpack_require__(16); -const which = __webpack_require__(128); -const pathKey = __webpack_require__(132)(); +const which = __webpack_require__(127); +const pathKey = __webpack_require__(131)(); function resolveCommandAttempt(parsed, withoutPathExt) { const cwd = process.cwd(); const hasCustomCwd = parsed.options.cwd != null; + // Worker threads do not have process.chdir() + const shouldSwitchCwd = hasCustomCwd && process.chdir !== undefined; // If a custom `cwd` was specified, we need to change the process cwd // because `which` will do stat calls but does not support a custom cwd - if (hasCustomCwd) { + if (shouldSwitchCwd) { try { process.chdir(parsed.options.cwd); } catch (err) { @@ -19753,7 +19599,9 @@ function resolveCommandAttempt(parsed, withoutPathExt) { } catch (e) { /* Empty */ } finally { - process.chdir(cwd); + if (shouldSwitchCwd) { + process.chdir(cwd); + } } // If we successfully resolved, ensure that an absolute path is returned @@ -19773,126 +19621,113 @@ module.exports = resolveCommand; /***/ }), -/* 128 */ +/* 127 */ /***/ (function(module, exports, __webpack_require__) { -module.exports = which -which.sync = whichSync - -var isWindows = process.platform === 'win32' || +const isWindows = process.platform === 'win32' || process.env.OSTYPE === 'cygwin' || process.env.OSTYPE === 'msys' -var path = __webpack_require__(16) -var COLON = isWindows ? ';' : ':' -var isexe = __webpack_require__(129) - -function getNotFoundError (cmd) { - var er = new Error('not found: ' + cmd) - er.code = 'ENOENT' +const path = __webpack_require__(16) +const COLON = isWindows ? ';' : ':' +const isexe = __webpack_require__(128) - return er -} +const getNotFoundError = (cmd) => + Object.assign(new Error(`not found: ${cmd}`), { code: 'ENOENT' }) -function getPathInfo (cmd, opt) { - var colon = opt.colon || COLON - var pathEnv = opt.path || process.env.PATH || '' - var pathExt = [''] +const getPathInfo = (cmd, opt) => { + const colon = opt.colon || COLON - pathEnv = pathEnv.split(colon) + // If it has a slash, then we don't bother searching the pathenv. + // just check the file itself, and that's it. + const pathEnv = cmd.match(/\//) || isWindows && cmd.match(/\\/) ? [''] + : ( + [ + // windows always checks the cwd first + ...(isWindows ? [process.cwd()] : []), + ...(opt.path || process.env.PATH || + /* istanbul ignore next: very unusual */ '').split(colon), + ] + ) + const pathExtExe = isWindows + ? opt.pathExt || process.env.PATHEXT || '.EXE;.CMD;.BAT;.COM' + : '' + const pathExt = isWindows ? pathExtExe.split(colon) : [''] - var pathExtExe = '' if (isWindows) { - pathEnv.unshift(process.cwd()) - pathExtExe = (opt.pathExt || process.env.PATHEXT || '.EXE;.CMD;.BAT;.COM') - pathExt = pathExtExe.split(colon) - - - // Always test the cmd itself first. isexe will check to make sure - // it's found in the pathExt set. if (cmd.indexOf('.') !== -1 && pathExt[0] !== '') pathExt.unshift('') } - // If it has a slash, then we don't bother searching the pathenv. - // just check the file itself, and that's it. - if (cmd.match(/\//) || isWindows && cmd.match(/\\/)) - pathEnv = [''] - return { - env: pathEnv, - ext: pathExt, - extExe: pathExtExe + pathEnv, + pathExt, + pathExtExe, } } -function which (cmd, opt, cb) { +const which = (cmd, opt, cb) => { if (typeof opt === 'function') { cb = opt opt = {} } + if (!opt) + opt = {} - var info = getPathInfo(cmd, opt) - var pathEnv = info.env - var pathExt = info.ext - var pathExtExe = info.extExe - var found = [] + const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt) + const found = [] - ;(function F (i, l) { - if (i === l) { - if (opt.all && found.length) - return cb(null, found) - else - return cb(getNotFoundError(cmd)) - } + const step = i => new Promise((resolve, reject) => { + if (i === pathEnv.length) + return opt.all && found.length ? resolve(found) + : reject(getNotFoundError(cmd)) - var pathPart = pathEnv[i] - if (pathPart.charAt(0) === '"' && pathPart.slice(-1) === '"') - pathPart = pathPart.slice(1, -1) + const ppRaw = pathEnv[i] + const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw - var p = path.join(pathPart, cmd) - if (!pathPart && (/^\.[\\\/]/).test(cmd)) { - p = cmd.slice(0, 2) + p - } - ;(function E (ii, ll) { - if (ii === ll) return F(i + 1, l) - var ext = pathExt[ii] - isexe(p + ext, { pathExt: pathExtExe }, function (er, is) { - if (!er && is) { - if (opt.all) - found.push(p + ext) - else - return cb(null, p + ext) - } - return E(ii + 1, ll) - }) - })(0, pathExt.length) - })(0, pathEnv.length) + const pCmd = path.join(pathPart, cmd) + const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd + : pCmd + + resolve(subStep(p, i, 0)) + }) + + const subStep = (p, i, ii) => new Promise((resolve, reject) => { + if (ii === pathExt.length) + return resolve(step(i + 1)) + const ext = pathExt[ii] + isexe(p + ext, { pathExt: pathExtExe }, (er, is) => { + if (!er && is) { + if (opt.all) + found.push(p + ext) + else + return resolve(p + ext) + } + return resolve(subStep(p, i, ii + 1)) + }) + }) + + return cb ? step(0).then(res => cb(null, res), cb) : step(0) } -function whichSync (cmd, opt) { +const whichSync = (cmd, opt) => { opt = opt || {} - var info = getPathInfo(cmd, opt) - var pathEnv = info.env - var pathExt = info.ext - var pathExtExe = info.extExe - var found = [] + const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt) + const found = [] - for (var i = 0, l = pathEnv.length; i < l; i ++) { - var pathPart = pathEnv[i] - if (pathPart.charAt(0) === '"' && pathPart.slice(-1) === '"') - pathPart = pathPart.slice(1, -1) + for (let i = 0; i < pathEnv.length; i ++) { + const ppRaw = pathEnv[i] + const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw - var p = path.join(pathPart, cmd) - if (!pathPart && /^\.[\\\/]/.test(cmd)) { - p = cmd.slice(0, 2) + p - } - for (var j = 0, ll = pathExt.length; j < ll; j ++) { - var cur = p + pathExt[j] - var is + const pCmd = path.join(pathPart, cmd) + const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd + : pCmd + + for (let j = 0; j < pathExt.length; j ++) { + const cur = p + pathExt[j] try { - is = isexe.sync(cur, { pathExt: pathExtExe }) + const is = isexe.sync(cur, { pathExt: pathExtExe }) if (is) { if (opt.all) found.push(cur) @@ -19912,17 +19747,20 @@ function whichSync (cmd, opt) { throw getNotFoundError(cmd) } +module.exports = which +which.sync = whichSync + /***/ }), -/* 129 */ +/* 128 */ /***/ (function(module, exports, __webpack_require__) { var fs = __webpack_require__(23) var core if (process.platform === 'win32' || global.TESTING_WINDOWS) { - core = __webpack_require__(130) + core = __webpack_require__(129) } else { - core = __webpack_require__(131) + core = __webpack_require__(130) } module.exports = isexe @@ -19977,7 +19815,7 @@ function sync (path, options) { /***/ }), -/* 130 */ +/* 129 */ /***/ (function(module, exports, __webpack_require__) { module.exports = isexe @@ -20025,7 +19863,7 @@ function sync (path, options) { /***/ }), -/* 131 */ +/* 130 */ /***/ (function(module, exports, __webpack_require__) { module.exports = isexe @@ -20072,27 +19910,30 @@ function checkMode (stat, options) { /***/ }), -/* 132 */ +/* 131 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -module.exports = opts => { - opts = opts || {}; - const env = opts.env || process.env; - const platform = opts.platform || process.platform; +const pathKey = (options = {}) => { + const environment = options.env || process.env; + const platform = options.platform || process.platform; if (platform !== 'win32') { return 'PATH'; } - return Object.keys(env).find(x => x.toUpperCase() === 'PATH') || 'Path'; + return Object.keys(environment).find(key => key.toUpperCase() === 'PATH') || 'Path'; }; +module.exports = pathKey; +// TODO: Remove this for the next major release +module.exports.default = pathKey; + /***/ }), -/* 133 */ +/* 132 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -20144,28 +19985,19 @@ module.exports.argument = escapeArgument; /***/ }), -/* 134 */ +/* 133 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(23); -const shebangCommand = __webpack_require__(135); +const shebangCommand = __webpack_require__(134); function readShebang(command) { // Read the first 150 bytes from the file const size = 150; - let buffer; - - if (Buffer.alloc) { - // Node.js v4.5+ / v5.10+ - buffer = Buffer.alloc(size); - } else { - // Old Node.js API - buffer = new Buffer(size); - buffer.fill(0); // zero-fill - } + const buffer = Buffer.alloc(size); let fd; @@ -20183,1560 +20015,1113 @@ module.exports = readShebang; /***/ }), -/* 135 */ +/* 134 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var shebangRegex = __webpack_require__(136); +const shebangRegex = __webpack_require__(135); -module.exports = function (str) { - var match = str.match(shebangRegex); +module.exports = (string = '') => { + const match = string.match(shebangRegex); if (!match) { return null; } - var arr = match[0].replace(/#! ?/, '').split(' '); - var bin = arr[0].split('/').pop(); - var arg = arr[1]; + const [path, argument] = match[0].replace(/#! ?/, '').split(' '); + const binary = path.split('/').pop(); - return (bin === 'env' ? - arg : - bin + (arg ? ' ' + arg : '') - ); + if (binary === 'env') { + return argument; + } + + return argument ? `${binary} ${argument}` : binary; }; /***/ }), -/* 136 */ +/* 135 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -module.exports = /^#!.*/; +module.exports = /^#!(.*)/; /***/ }), -/* 137 */ -/***/ (function(module, exports) { +/* 136 */ +/***/ (function(module, exports, __webpack_require__) { -exports = module.exports = SemVer; +"use strict"; -// The debug function is excluded entirely from the minified version. -/* nomin */ var debug; -/* nomin */ if (typeof process === 'object' && - /* nomin */ process.env && - /* nomin */ process.env.NODE_DEBUG && - /* nomin */ /\bsemver\b/i.test(process.env.NODE_DEBUG)) - /* nomin */ debug = function() { - /* nomin */ var args = Array.prototype.slice.call(arguments, 0); - /* nomin */ args.unshift('SEMVER'); - /* nomin */ console.log.apply(console, args); - /* nomin */ }; -/* nomin */ else - /* nomin */ debug = function() {}; -// Note: this is the semver.org version of the spec that it implements -// Not necessarily the package version of this code. -exports.SEMVER_SPEC_VERSION = '2.0.0'; +const isWin = process.platform === 'win32'; -var MAX_LENGTH = 256; -var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991; +function notFoundError(original, syscall) { + return Object.assign(new Error(`${syscall} ${original.command} ENOENT`), { + code: 'ENOENT', + errno: 'ENOENT', + syscall: `${syscall} ${original.command}`, + path: original.command, + spawnargs: original.args, + }); +} -// Max safe segment length for coercion. -var MAX_SAFE_COMPONENT_LENGTH = 16; +function hookChildProcess(cp, parsed) { + if (!isWin) { + return; + } -// The actual regexps go on exports.re -var re = exports.re = []; -var src = exports.src = []; -var R = 0; + const originalEmit = cp.emit; -// The following Regular Expressions can be used for tokenizing, -// validating, and parsing SemVer version strings. + cp.emit = function (name, arg1) { + // If emitting "exit" event and exit code is 1, we need to check if + // the command exists and emit an "error" instead + // See https://github.com/IndigoUnited/node-cross-spawn/issues/16 + if (name === 'exit') { + const err = verifyENOENT(arg1, parsed, 'spawn'); -// ## Numeric Identifier -// A single `0`, or a non-zero digit followed by zero or more digits. + if (err) { + return originalEmit.call(cp, 'error', err); + } + } -var NUMERICIDENTIFIER = R++; -src[NUMERICIDENTIFIER] = '0|[1-9]\\d*'; -var NUMERICIDENTIFIERLOOSE = R++; -src[NUMERICIDENTIFIERLOOSE] = '[0-9]+'; + return originalEmit.apply(cp, arguments); // eslint-disable-line prefer-rest-params + }; +} +function verifyENOENT(status, parsed) { + if (isWin && status === 1 && !parsed.file) { + return notFoundError(parsed.original, 'spawn'); + } -// ## Non-numeric Identifier -// Zero or more digits, followed by a letter or hyphen, and then zero or -// more letters, digits, or hyphens. + return null; +} -var NONNUMERICIDENTIFIER = R++; -src[NONNUMERICIDENTIFIER] = '\\d*[a-zA-Z-][a-zA-Z0-9-]*'; +function verifyENOENTSync(status, parsed) { + if (isWin && status === 1 && !parsed.file) { + return notFoundError(parsed.original, 'spawnSync'); + } + return null; +} -// ## Main Version -// Three dot-separated numeric identifiers. +module.exports = { + hookChildProcess, + verifyENOENT, + verifyENOENTSync, + notFoundError, +}; -var MAINVERSION = R++; -src[MAINVERSION] = '(' + src[NUMERICIDENTIFIER] + ')\\.' + - '(' + src[NUMERICIDENTIFIER] + ')\\.' + - '(' + src[NUMERICIDENTIFIER] + ')'; -var MAINVERSIONLOOSE = R++; -src[MAINVERSIONLOOSE] = '(' + src[NUMERICIDENTIFIERLOOSE] + ')\\.' + - '(' + src[NUMERICIDENTIFIERLOOSE] + ')\\.' + - '(' + src[NUMERICIDENTIFIERLOOSE] + ')'; +/***/ }), +/* 137 */ +/***/ (function(module, exports, __webpack_require__) { -// ## Pre-release Version Identifier -// A numeric identifier, or a non-numeric identifier. +"use strict"; -var PRERELEASEIDENTIFIER = R++; -src[PRERELEASEIDENTIFIER] = '(?:' + src[NUMERICIDENTIFIER] + - '|' + src[NONNUMERICIDENTIFIER] + ')'; -var PRERELEASEIDENTIFIERLOOSE = R++; -src[PRERELEASEIDENTIFIERLOOSE] = '(?:' + src[NUMERICIDENTIFIERLOOSE] + - '|' + src[NONNUMERICIDENTIFIER] + ')'; +module.exports = input => { + const LF = typeof input === 'string' ? '\n' : '\n'.charCodeAt(); + const CR = typeof input === 'string' ? '\r' : '\r'.charCodeAt(); + if (input[input.length - 1] === LF) { + input = input.slice(0, input.length - 1); + } -// ## Pre-release Version -// Hyphen, followed by one or more dot-separated pre-release version -// identifiers. + if (input[input.length - 1] === CR) { + input = input.slice(0, input.length - 1); + } -var PRERELEASE = R++; -src[PRERELEASE] = '(?:-(' + src[PRERELEASEIDENTIFIER] + - '(?:\\.' + src[PRERELEASEIDENTIFIER] + ')*))'; + return input; +}; -var PRERELEASELOOSE = R++; -src[PRERELEASELOOSE] = '(?:-?(' + src[PRERELEASEIDENTIFIERLOOSE] + - '(?:\\.' + src[PRERELEASEIDENTIFIERLOOSE] + ')*))'; -// ## Build Metadata Identifier -// Any combination of digits, letters, or hyphens. +/***/ }), +/* 138 */ +/***/ (function(module, exports, __webpack_require__) { -var BUILDIDENTIFIER = R++; -src[BUILDIDENTIFIER] = '[0-9A-Za-z-]+'; +"use strict"; -// ## Build Metadata -// Plus sign, followed by one or more period-separated build metadata -// identifiers. +const path = __webpack_require__(16); +const pathKey = __webpack_require__(131); -var BUILD = R++; -src[BUILD] = '(?:\\+(' + src[BUILDIDENTIFIER] + - '(?:\\.' + src[BUILDIDENTIFIER] + ')*))'; +const npmRunPath = options => { + options = { + cwd: process.cwd(), + path: process.env[pathKey()], + execPath: process.execPath, + ...options + }; + let previous; + let cwdPath = path.resolve(options.cwd); + const result = []; -// ## Full Version String -// A main version, followed optionally by a pre-release version and -// build metadata. + while (previous !== cwdPath) { + result.push(path.join(cwdPath, 'node_modules/.bin')); + previous = cwdPath; + cwdPath = path.resolve(cwdPath, '..'); + } -// Note that the only major, minor, patch, and pre-release sections of -// the version string are capturing groups. The build metadata is not a -// capturing group, because it should not ever be used in version -// comparison. + // Ensure the running `node` binary is used + const execPathDir = path.resolve(options.cwd, options.execPath, '..'); + result.unshift(execPathDir); -var FULL = R++; -var FULLPLAIN = 'v?' + src[MAINVERSION] + - src[PRERELEASE] + '?' + - src[BUILD] + '?'; + return result.concat(options.path).join(path.delimiter); +}; -src[FULL] = '^' + FULLPLAIN + '$'; +module.exports = npmRunPath; +// TODO: Remove this for the next major release +module.exports.default = npmRunPath; -// like full, but allows v1.2.3 and =1.2.3, which people do sometimes. -// also, 1.0.0alpha1 (prerelease without the hyphen) which is pretty -// common in the npm registry. -var LOOSEPLAIN = '[v=\\s]*' + src[MAINVERSIONLOOSE] + - src[PRERELEASELOOSE] + '?' + - src[BUILD] + '?'; +module.exports.env = options => { + options = { + env: process.env, + ...options + }; -var LOOSE = R++; -src[LOOSE] = '^' + LOOSEPLAIN + '$'; + const env = {...options.env}; + const path = pathKey({env}); -var GTLT = R++; -src[GTLT] = '((?:<|>)?=?)'; + options.path = env[path]; + env[path] = module.exports(options); -// Something like "2.*" or "1.2.x". -// Note that "x.x" is a valid xRange identifer, meaning "any version" -// Only the first item is strictly required. -var XRANGEIDENTIFIERLOOSE = R++; -src[XRANGEIDENTIFIERLOOSE] = src[NUMERICIDENTIFIERLOOSE] + '|x|X|\\*'; -var XRANGEIDENTIFIER = R++; -src[XRANGEIDENTIFIER] = src[NUMERICIDENTIFIER] + '|x|X|\\*'; + return env; +}; -var XRANGEPLAIN = R++; -src[XRANGEPLAIN] = '[v=\\s]*(' + src[XRANGEIDENTIFIER] + ')' + - '(?:\\.(' + src[XRANGEIDENTIFIER] + ')' + - '(?:\\.(' + src[XRANGEIDENTIFIER] + ')' + - '(?:' + src[PRERELEASE] + ')?' + - src[BUILD] + '?' + - ')?)?'; -var XRANGEPLAINLOOSE = R++; -src[XRANGEPLAINLOOSE] = '[v=\\s]*(' + src[XRANGEIDENTIFIERLOOSE] + ')' + - '(?:\\.(' + src[XRANGEIDENTIFIERLOOSE] + ')' + - '(?:\\.(' + src[XRANGEIDENTIFIERLOOSE] + ')' + - '(?:' + src[PRERELEASELOOSE] + ')?' + - src[BUILD] + '?' + - ')?)?'; +/***/ }), +/* 139 */ +/***/ (function(module, exports, __webpack_require__) { -var XRANGE = R++; -src[XRANGE] = '^' + src[GTLT] + '\\s*' + src[XRANGEPLAIN] + '$'; -var XRANGELOOSE = R++; -src[XRANGELOOSE] = '^' + src[GTLT] + '\\s*' + src[XRANGEPLAINLOOSE] + '$'; +"use strict"; -// Coercion. -// Extract anything that could conceivably be a part of a valid semver -var COERCE = R++; -src[COERCE] = '(?:^|[^\\d])' + - '(\\d{1,' + MAX_SAFE_COMPONENT_LENGTH + '})' + - '(?:\\.(\\d{1,' + MAX_SAFE_COMPONENT_LENGTH + '}))?' + - '(?:\\.(\\d{1,' + MAX_SAFE_COMPONENT_LENGTH + '}))?' + - '(?:$|[^\\d])'; +const mimicFn = __webpack_require__(140); -// Tilde ranges. -// Meaning is "reasonably at or greater than" -var LONETILDE = R++; -src[LONETILDE] = '(?:~>?)'; +const calledFunctions = new WeakMap(); -var TILDETRIM = R++; -src[TILDETRIM] = '(\\s*)' + src[LONETILDE] + '\\s+'; -re[TILDETRIM] = new RegExp(src[TILDETRIM], 'g'); -var tildeTrimReplace = '$1~'; +const oneTime = (fn, options = {}) => { + if (typeof fn !== 'function') { + throw new TypeError('Expected a function'); + } -var TILDE = R++; -src[TILDE] = '^' + src[LONETILDE] + src[XRANGEPLAIN] + '$'; -var TILDELOOSE = R++; -src[TILDELOOSE] = '^' + src[LONETILDE] + src[XRANGEPLAINLOOSE] + '$'; + let ret; + let isCalled = false; + let callCount = 0; + const functionName = fn.displayName || fn.name || ''; -// Caret ranges. -// Meaning is "at least and backwards compatible with" -var LONECARET = R++; -src[LONECARET] = '(?:\\^)'; + const onetime = function (...args) { + calledFunctions.set(onetime, ++callCount); -var CARETTRIM = R++; -src[CARETTRIM] = '(\\s*)' + src[LONECARET] + '\\s+'; -re[CARETTRIM] = new RegExp(src[CARETTRIM], 'g'); -var caretTrimReplace = '$1^'; + if (isCalled) { + if (options.throw === true) { + throw new Error(`Function \`${functionName}\` can only be called once`); + } -var CARET = R++; -src[CARET] = '^' + src[LONECARET] + src[XRANGEPLAIN] + '$'; -var CARETLOOSE = R++; -src[CARETLOOSE] = '^' + src[LONECARET] + src[XRANGEPLAINLOOSE] + '$'; + return ret; + } -// A simple gt/lt/eq thing, or just "" to indicate "any version" -var COMPARATORLOOSE = R++; -src[COMPARATORLOOSE] = '^' + src[GTLT] + '\\s*(' + LOOSEPLAIN + ')$|^$'; -var COMPARATOR = R++; -src[COMPARATOR] = '^' + src[GTLT] + '\\s*(' + FULLPLAIN + ')$|^$'; + isCalled = true; + ret = fn.apply(this, args); + fn = null; + return ret; + }; -// An expression to strip any whitespace between the gtlt and the thing -// it modifies, so that `> 1.2.3` ==> `>1.2.3` -var COMPARATORTRIM = R++; -src[COMPARATORTRIM] = '(\\s*)' + src[GTLT] + - '\\s*(' + LOOSEPLAIN + '|' + src[XRANGEPLAIN] + ')'; + mimicFn(onetime, fn); + calledFunctions.set(onetime, callCount); -// this one has to use the /g flag -re[COMPARATORTRIM] = new RegExp(src[COMPARATORTRIM], 'g'); -var comparatorTrimReplace = '$1$2$3'; + return onetime; +}; +module.exports = oneTime; +// TODO: Remove this for the next major release +module.exports.default = oneTime; -// Something like `1.2.3 - 1.2.4` -// Note that these all use the loose form, because they'll be -// checked against either the strict or loose comparator form -// later. -var HYPHENRANGE = R++; -src[HYPHENRANGE] = '^\\s*(' + src[XRANGEPLAIN] + ')' + - '\\s+-\\s+' + - '(' + src[XRANGEPLAIN] + ')' + - '\\s*$'; +module.exports.callCount = fn => { + if (!calledFunctions.has(fn)) { + throw new Error(`The given function \`${fn.name}\` is not wrapped by the \`onetime\` package`); + } -var HYPHENRANGELOOSE = R++; -src[HYPHENRANGELOOSE] = '^\\s*(' + src[XRANGEPLAINLOOSE] + ')' + - '\\s+-\\s+' + - '(' + src[XRANGEPLAINLOOSE] + ')' + - '\\s*$'; + return calledFunctions.get(fn); +}; -// Star ranges basically just allow anything at all. -var STAR = R++; -src[STAR] = '(<|>)?=?\\s*\\*'; -// Compile to actual regexp objects. -// All are flag-free, unless they were created above with a flag. -for (var i = 0; i < R; i++) { - debug(i, src[i]); - if (!re[i]) - re[i] = new RegExp(src[i]); -} +/***/ }), +/* 140 */ +/***/ (function(module, exports, __webpack_require__) { -exports.parse = parse; -function parse(version, loose) { - if (version instanceof SemVer) - return version; +"use strict"; - if (typeof version !== 'string') - return null; - if (version.length > MAX_LENGTH) - return null; +const mimicFn = (to, from) => { + for (const prop of Reflect.ownKeys(from)) { + Object.defineProperty(to, prop, Object.getOwnPropertyDescriptor(from, prop)); + } - var r = loose ? re[LOOSE] : re[FULL]; - if (!r.test(version)) - return null; + return to; +}; - try { - return new SemVer(version, loose); - } catch (er) { - return null; - } -} +module.exports = mimicFn; +// TODO: Remove this for the next major release +module.exports.default = mimicFn; -exports.valid = valid; -function valid(version, loose) { - var v = parse(version, loose); - return v ? v.version : null; -} +/***/ }), +/* 141 */ +/***/ (function(module, exports, __webpack_require__) { -exports.clean = clean; -function clean(version, loose) { - var s = parse(version.trim().replace(/^[=v]+/, ''), loose); - return s ? s.version : null; -} +"use strict"; -exports.SemVer = SemVer; +const {signalsByName} = __webpack_require__(142); -function SemVer(version, loose) { - if (version instanceof SemVer) { - if (version.loose === loose) - return version; - else - version = version.version; - } else if (typeof version !== 'string') { - throw new TypeError('Invalid Version: ' + version); - } +const getErrorPrefix = ({timedOut, timeout, errorCode, signal, signalDescription, exitCode, isCanceled}) => { + if (timedOut) { + return `timed out after ${timeout} milliseconds`; + } - if (version.length > MAX_LENGTH) - throw new TypeError('version is longer than ' + MAX_LENGTH + ' characters') + if (isCanceled) { + return 'was canceled'; + } - if (!(this instanceof SemVer)) - return new SemVer(version, loose); + if (errorCode !== undefined) { + return `failed with ${errorCode}`; + } - debug('SemVer', version, loose); - this.loose = loose; - var m = version.trim().match(loose ? re[LOOSE] : re[FULL]); + if (signal !== undefined) { + return `was killed with ${signal} (${signalDescription})`; + } - if (!m) - throw new TypeError('Invalid Version: ' + version); + if (exitCode !== undefined) { + return `failed with exit code ${exitCode}`; + } - this.raw = version; + return 'failed'; +}; + +const makeError = ({ + stdout, + stderr, + all, + error, + signal, + exitCode, + command, + timedOut, + isCanceled, + killed, + parsed: {options: {timeout}} +}) => { + // `signal` and `exitCode` emitted on `spawned.on('exit')` event can be `null`. + // We normalize them to `undefined` + exitCode = exitCode === null ? undefined : exitCode; + signal = signal === null ? undefined : signal; + const signalDescription = signal === undefined ? undefined : signalsByName[signal].description; + + const errorCode = error && error.code; + + const prefix = getErrorPrefix({timedOut, timeout, errorCode, signal, signalDescription, exitCode, isCanceled}); + const message = `Command ${prefix}: ${command}`; + + if (error instanceof Error) { + error.originalMessage = error.message; + error.message = `${message}\n${error.message}`; + } else { + error = new Error(message); + } - // these are actually numbers - this.major = +m[1]; - this.minor = +m[2]; - this.patch = +m[3]; + error.command = command; + error.exitCode = exitCode; + error.signal = signal; + error.signalDescription = signalDescription; + error.stdout = stdout; + error.stderr = stderr; - if (this.major > MAX_SAFE_INTEGER || this.major < 0) - throw new TypeError('Invalid major version') + if (all !== undefined) { + error.all = all; + } - if (this.minor > MAX_SAFE_INTEGER || this.minor < 0) - throw new TypeError('Invalid minor version') + if ('bufferedData' in error) { + delete error.bufferedData; + } - if (this.patch > MAX_SAFE_INTEGER || this.patch < 0) - throw new TypeError('Invalid patch version') + error.failed = true; + error.timedOut = Boolean(timedOut); + error.isCanceled = isCanceled; + error.killed = killed && !timedOut; - // numberify any prerelease numeric ids - if (!m[4]) - this.prerelease = []; - else - this.prerelease = m[4].split('.').map(function(id) { - if (/^[0-9]+$/.test(id)) { - var num = +id; - if (num >= 0 && num < MAX_SAFE_INTEGER) - return num; - } - return id; - }); + return error; +}; - this.build = m[5] ? m[5].split('.') : []; - this.format(); -} +module.exports = makeError; -SemVer.prototype.format = function() { - this.version = this.major + '.' + this.minor + '.' + this.patch; - if (this.prerelease.length) - this.version += '-' + this.prerelease.join('.'); - return this.version; -}; -SemVer.prototype.toString = function() { - return this.version; -}; +/***/ }), +/* 142 */ +/***/ (function(module, exports, __webpack_require__) { -SemVer.prototype.compare = function(other) { - debug('SemVer.compare', this.version, this.loose, other); - if (!(other instanceof SemVer)) - other = new SemVer(other, this.loose); +"use strict"; +Object.defineProperty(exports,"__esModule",{value:true});exports.signalsByNumber=exports.signalsByName=void 0;var _os=__webpack_require__(11); - return this.compareMain(other) || this.comparePre(other); +var _signals=__webpack_require__(143); +var _realtime=__webpack_require__(145); + + + +const getSignalsByName=function(){ +const signals=(0,_signals.getSignals)(); +return signals.reduce(getSignalByName,{}); }; -SemVer.prototype.compareMain = function(other) { - if (!(other instanceof SemVer)) - other = new SemVer(other, this.loose); +const getSignalByName=function( +signalByNameMemo, +{name,number,description,supported,action,forced,standard}) +{ +return{ +...signalByNameMemo, +[name]:{name,number,description,supported,action,forced,standard}}; - return compareIdentifiers(this.major, other.major) || - compareIdentifiers(this.minor, other.minor) || - compareIdentifiers(this.patch, other.patch); }; -SemVer.prototype.comparePre = function(other) { - if (!(other instanceof SemVer)) - other = new SemVer(other, this.loose); +const signalsByName=getSignalsByName();exports.signalsByName=signalsByName; - // NOT having a prerelease is > having one - if (this.prerelease.length && !other.prerelease.length) - return -1; - else if (!this.prerelease.length && other.prerelease.length) - return 1; - else if (!this.prerelease.length && !other.prerelease.length) - return 0; - var i = 0; - do { - var a = this.prerelease[i]; - var b = other.prerelease[i]; - debug('prerelease compare', i, a, b); - if (a === undefined && b === undefined) - return 0; - else if (b === undefined) - return 1; - else if (a === undefined) - return -1; - else if (a === b) - continue; - else - return compareIdentifiers(a, b); - } while (++i); -}; -// preminor will bump the version up to the next minor release, and immediately -// down to pre-release. premajor and prepatch work the same way. -SemVer.prototype.inc = function(release, identifier) { - switch (release) { - case 'premajor': - this.prerelease.length = 0; - this.patch = 0; - this.minor = 0; - this.major++; - this.inc('pre', identifier); - break; - case 'preminor': - this.prerelease.length = 0; - this.patch = 0; - this.minor++; - this.inc('pre', identifier); - break; - case 'prepatch': - // If this is already a prerelease, it will bump to the next version - // drop any prereleases that might already exist, since they are not - // relevant at this point. - this.prerelease.length = 0; - this.inc('patch', identifier); - this.inc('pre', identifier); - break; - // If the input is a non-prerelease version, this acts the same as - // prepatch. - case 'prerelease': - if (this.prerelease.length === 0) - this.inc('patch', identifier); - this.inc('pre', identifier); - break; - case 'major': - // If this is a pre-major version, bump up to the same major version. - // Otherwise increment major. - // 1.0.0-5 bumps to 1.0.0 - // 1.1.0 bumps to 2.0.0 - if (this.minor !== 0 || this.patch !== 0 || this.prerelease.length === 0) - this.major++; - this.minor = 0; - this.patch = 0; - this.prerelease = []; - break; - case 'minor': - // If this is a pre-minor version, bump up to the same minor version. - // Otherwise increment minor. - // 1.2.0-5 bumps to 1.2.0 - // 1.2.1 bumps to 1.3.0 - if (this.patch !== 0 || this.prerelease.length === 0) - this.minor++; - this.patch = 0; - this.prerelease = []; - break; - case 'patch': - // If this is not a pre-release version, it will increment the patch. - // If it is a pre-release it will bump up to the same patch version. - // 1.2.0-5 patches to 1.2.0 - // 1.2.0 patches to 1.2.1 - if (this.prerelease.length === 0) - this.patch++; - this.prerelease = []; - break; - // This probably shouldn't be used publicly. - // 1.0.0 "pre" would become 1.0.0-0 which is the wrong direction. - case 'pre': - if (this.prerelease.length === 0) - this.prerelease = [0]; - else { - var i = this.prerelease.length; - while (--i >= 0) { - if (typeof this.prerelease[i] === 'number') { - this.prerelease[i]++; - i = -2; - } - } - if (i === -1) // didn't increment anything - this.prerelease.push(0); - } - if (identifier) { - // 1.2.0-beta.1 bumps to 1.2.0-beta.2, - // 1.2.0-beta.fooblz or 1.2.0-beta bumps to 1.2.0-beta.0 - if (this.prerelease[0] === identifier) { - if (isNaN(this.prerelease[1])) - this.prerelease = [identifier, 0]; - } else - this.prerelease = [identifier, 0]; - } - break; +const getSignalsByNumber=function(){ +const signals=(0,_signals.getSignals)(); +const length=_realtime.SIGRTMAX+1; +const signalsA=Array.from({length},(value,number)=> +getSignalByNumber(number,signals)); - default: - throw new Error('invalid increment argument: ' + release); - } - this.format(); - this.raw = this.version; - return this; +return Object.assign({},...signalsA); }; -exports.inc = inc; -function inc(version, release, loose, identifier) { - if (typeof(loose) === 'string') { - identifier = loose; - loose = undefined; - } +const getSignalByNumber=function(number,signals){ +const signal=findSignalByNumber(number,signals); - try { - return new SemVer(version, loose).inc(release, identifier).version; - } catch (er) { - return null; - } +if(signal===undefined){ +return{}; } -exports.diff = diff; -function diff(version1, version2) { - if (eq(version1, version2)) { - return null; - } else { - var v1 = parse(version1); - var v2 = parse(version2); - if (v1.prerelease.length || v2.prerelease.length) { - for (var key in v1) { - if (key === 'major' || key === 'minor' || key === 'patch') { - if (v1[key] !== v2[key]) { - return 'pre'+key; - } - } - } - return 'prerelease'; - } - for (var key in v1) { - if (key === 'major' || key === 'minor' || key === 'patch') { - if (v1[key] !== v2[key]) { - return key; - } - } - } - } -} +const{name,description,supported,action,forced,standard}=signal; +return{ +[number]:{ +name, +number, +description, +supported, +action, +forced, +standard}}; -exports.compareIdentifiers = compareIdentifiers; -var numeric = /^[0-9]+$/; -function compareIdentifiers(a, b) { - var anum = numeric.test(a); - var bnum = numeric.test(b); +}; - if (anum && bnum) { - a = +a; - b = +b; - } - return (anum && !bnum) ? -1 : - (bnum && !anum) ? 1 : - a < b ? -1 : - a > b ? 1 : - 0; -} -exports.rcompareIdentifiers = rcompareIdentifiers; -function rcompareIdentifiers(a, b) { - return compareIdentifiers(b, a); -} +const findSignalByNumber=function(number,signals){ +const signal=signals.find(({name})=>_os.constants.signals[name]===number); -exports.major = major; -function major(a, loose) { - return new SemVer(a, loose).major; +if(signal!==undefined){ +return signal; } -exports.minor = minor; -function minor(a, loose) { - return new SemVer(a, loose).minor; -} +return signals.find(signalA=>signalA.number===number); +}; -exports.patch = patch; -function patch(a, loose) { - return new SemVer(a, loose).patch; -} +const signalsByNumber=getSignalsByNumber();exports.signalsByNumber=signalsByNumber; +//# sourceMappingURL=main.js.map -exports.compare = compare; -function compare(a, b, loose) { - return new SemVer(a, loose).compare(new SemVer(b, loose)); -} +/***/ }), +/* 143 */ +/***/ (function(module, exports, __webpack_require__) { -exports.compareLoose = compareLoose; -function compareLoose(a, b) { - return compare(a, b, true); -} +"use strict"; +Object.defineProperty(exports,"__esModule",{value:true});exports.getSignals=void 0;var _os=__webpack_require__(11); -exports.rcompare = rcompare; -function rcompare(a, b, loose) { - return compare(b, a, loose); -} +var _core=__webpack_require__(144); +var _realtime=__webpack_require__(145); -exports.sort = sort; -function sort(list, loose) { - return list.sort(function(a, b) { - return exports.compare(a, b, loose); - }); -} -exports.rsort = rsort; -function rsort(list, loose) { - return list.sort(function(a, b) { - return exports.rcompare(a, b, loose); - }); -} -exports.gt = gt; -function gt(a, b, loose) { - return compare(a, b, loose) > 0; -} +const getSignals=function(){ +const realtimeSignals=(0,_realtime.getRealtimeSignals)(); +const signals=[..._core.SIGNALS,...realtimeSignals].map(normalizeSignal); +return signals; +};exports.getSignals=getSignals; -exports.lt = lt; -function lt(a, b, loose) { - return compare(a, b, loose) < 0; -} -exports.eq = eq; -function eq(a, b, loose) { - return compare(a, b, loose) === 0; -} -exports.neq = neq; -function neq(a, b, loose) { - return compare(a, b, loose) !== 0; -} -exports.gte = gte; -function gte(a, b, loose) { - return compare(a, b, loose) >= 0; -} -exports.lte = lte; -function lte(a, b, loose) { - return compare(a, b, loose) <= 0; -} -exports.cmp = cmp; -function cmp(a, op, b, loose) { - var ret; - switch (op) { - case '===': - if (typeof a === 'object') a = a.version; - if (typeof b === 'object') b = b.version; - ret = a === b; - break; - case '!==': - if (typeof a === 'object') a = a.version; - if (typeof b === 'object') b = b.version; - ret = a !== b; - break; - case '': case '=': case '==': ret = eq(a, b, loose); break; - case '!=': ret = neq(a, b, loose); break; - case '>': ret = gt(a, b, loose); break; - case '>=': ret = gte(a, b, loose); break; - case '<': ret = lt(a, b, loose); break; - case '<=': ret = lte(a, b, loose); break; - default: throw new TypeError('Invalid operator: ' + op); - } - return ret; -} -exports.Comparator = Comparator; -function Comparator(comp, loose) { - if (comp instanceof Comparator) { - if (comp.loose === loose) - return comp; - else - comp = comp.value; - } +const normalizeSignal=function({ +name, +number:defaultNumber, +description, +action, +forced=false, +standard}) +{ +const{ +signals:{[name]:constantSignal}}= +_os.constants; +const supported=constantSignal!==undefined; +const number=supported?constantSignal:defaultNumber; +return{name,number,description,supported,action,forced,standard}; +}; +//# sourceMappingURL=signals.js.map - if (!(this instanceof Comparator)) - return new Comparator(comp, loose); +/***/ }), +/* 144 */ +/***/ (function(module, exports, __webpack_require__) { - debug('comparator', comp, loose); - this.loose = loose; - this.parse(comp); +"use strict"; +Object.defineProperty(exports,"__esModule",{value:true});exports.SIGNALS=void 0; + +const SIGNALS=[ +{ +name:"SIGHUP", +number:1, +action:"terminate", +description:"Terminal closed", +standard:"posix"}, + +{ +name:"SIGINT", +number:2, +action:"terminate", +description:"User interruption with CTRL-C", +standard:"ansi"}, + +{ +name:"SIGQUIT", +number:3, +action:"core", +description:"User interruption with CTRL-\\", +standard:"posix"}, + +{ +name:"SIGILL", +number:4, +action:"core", +description:"Invalid machine instruction", +standard:"ansi"}, + +{ +name:"SIGTRAP", +number:5, +action:"core", +description:"Debugger breakpoint", +standard:"posix"}, + +{ +name:"SIGABRT", +number:6, +action:"core", +description:"Aborted", +standard:"ansi"}, + +{ +name:"SIGIOT", +number:6, +action:"core", +description:"Aborted", +standard:"bsd"}, + +{ +name:"SIGBUS", +number:7, +action:"core", +description: +"Bus error due to misaligned, non-existing address or paging error", +standard:"bsd"}, + +{ +name:"SIGEMT", +number:7, +action:"terminate", +description:"Command should be emulated but is not implemented", +standard:"other"}, + +{ +name:"SIGFPE", +number:8, +action:"core", +description:"Floating point arithmetic error", +standard:"ansi"}, + +{ +name:"SIGKILL", +number:9, +action:"terminate", +description:"Forced termination", +standard:"posix", +forced:true}, + +{ +name:"SIGUSR1", +number:10, +action:"terminate", +description:"Application-specific signal", +standard:"posix"}, + +{ +name:"SIGSEGV", +number:11, +action:"core", +description:"Segmentation fault", +standard:"ansi"}, + +{ +name:"SIGUSR2", +number:12, +action:"terminate", +description:"Application-specific signal", +standard:"posix"}, + +{ +name:"SIGPIPE", +number:13, +action:"terminate", +description:"Broken pipe or socket", +standard:"posix"}, + +{ +name:"SIGALRM", +number:14, +action:"terminate", +description:"Timeout or timer", +standard:"posix"}, + +{ +name:"SIGTERM", +number:15, +action:"terminate", +description:"Termination", +standard:"ansi"}, + +{ +name:"SIGSTKFLT", +number:16, +action:"terminate", +description:"Stack is empty or overflowed", +standard:"other"}, + +{ +name:"SIGCHLD", +number:17, +action:"ignore", +description:"Child process terminated, paused or unpaused", +standard:"posix"}, + +{ +name:"SIGCLD", +number:17, +action:"ignore", +description:"Child process terminated, paused or unpaused", +standard:"other"}, + +{ +name:"SIGCONT", +number:18, +action:"unpause", +description:"Unpaused", +standard:"posix", +forced:true}, + +{ +name:"SIGSTOP", +number:19, +action:"pause", +description:"Paused", +standard:"posix", +forced:true}, + +{ +name:"SIGTSTP", +number:20, +action:"pause", +description:"Paused using CTRL-Z or \"suspend\"", +standard:"posix"}, + +{ +name:"SIGTTIN", +number:21, +action:"pause", +description:"Background process cannot read terminal input", +standard:"posix"}, + +{ +name:"SIGBREAK", +number:21, +action:"terminate", +description:"User interruption with CTRL-BREAK", +standard:"other"}, + +{ +name:"SIGTTOU", +number:22, +action:"pause", +description:"Background process cannot write to terminal output", +standard:"posix"}, + +{ +name:"SIGURG", +number:23, +action:"ignore", +description:"Socket received out-of-band data", +standard:"bsd"}, + +{ +name:"SIGXCPU", +number:24, +action:"core", +description:"Process timed out", +standard:"bsd"}, + +{ +name:"SIGXFSZ", +number:25, +action:"core", +description:"File too big", +standard:"bsd"}, + +{ +name:"SIGVTALRM", +number:26, +action:"terminate", +description:"Timeout or timer", +standard:"bsd"}, + +{ +name:"SIGPROF", +number:27, +action:"terminate", +description:"Timeout or timer", +standard:"bsd"}, + +{ +name:"SIGWINCH", +number:28, +action:"ignore", +description:"Terminal window size changed", +standard:"bsd"}, + +{ +name:"SIGIO", +number:29, +action:"terminate", +description:"I/O is available", +standard:"other"}, + +{ +name:"SIGPOLL", +number:29, +action:"terminate", +description:"Watched event", +standard:"other"}, + +{ +name:"SIGINFO", +number:29, +action:"ignore", +description:"Request for process information", +standard:"other"}, + +{ +name:"SIGPWR", +number:30, +action:"terminate", +description:"Device running out of power", +standard:"systemv"}, + +{ +name:"SIGSYS", +number:31, +action:"core", +description:"Invalid system call", +standard:"other"}, + +{ +name:"SIGUNUSED", +number:31, +action:"terminate", +description:"Invalid system call", +standard:"other"}];exports.SIGNALS=SIGNALS; +//# sourceMappingURL=core.js.map - if (this.semver === ANY) - this.value = ''; - else - this.value = this.operator + this.semver.version; +/***/ }), +/* 145 */ +/***/ (function(module, exports, __webpack_require__) { - debug('comp', this); -} +"use strict"; +Object.defineProperty(exports,"__esModule",{value:true});exports.SIGRTMAX=exports.getRealtimeSignals=void 0; +const getRealtimeSignals=function(){ +const length=SIGRTMAX-SIGRTMIN+1; +return Array.from({length},getRealtimeSignal); +};exports.getRealtimeSignals=getRealtimeSignals; -var ANY = {}; -Comparator.prototype.parse = function(comp) { - var r = this.loose ? re[COMPARATORLOOSE] : re[COMPARATOR]; - var m = comp.match(r); +const getRealtimeSignal=function(value,index){ +return{ +name:`SIGRT${index+1}`, +number:SIGRTMIN+index, +action:"terminate", +description:"Application-specific signal (realtime)", +standard:"posix"}; - if (!m) - throw new TypeError('Invalid comparator: ' + comp); +}; - this.operator = m[1]; - if (this.operator === '=') - this.operator = ''; +const SIGRTMIN=34; +const SIGRTMAX=64;exports.SIGRTMAX=SIGRTMAX; +//# sourceMappingURL=realtime.js.map - // if it literally is just '>' or '' then allow anything. - if (!m[2]) - this.semver = ANY; - else - this.semver = new SemVer(m[2], this.loose); -}; +/***/ }), +/* 146 */ +/***/ (function(module, exports, __webpack_require__) { -Comparator.prototype.toString = function() { - return this.value; -}; +"use strict"; -Comparator.prototype.test = function(version) { - debug('Comparator.test', version, this.loose); +const aliases = ['stdin', 'stdout', 'stderr']; - if (this.semver === ANY) - return true; +const hasAlias = opts => aliases.some(alias => opts[alias] !== undefined); - if (typeof version === 'string') - version = new SemVer(version, this.loose); +const normalizeStdio = opts => { + if (!opts) { + return; + } - return cmp(version, this.operator, this.semver, this.loose); -}; + const {stdio} = opts; -Comparator.prototype.intersects = function(comp, loose) { - if (!(comp instanceof Comparator)) { - throw new TypeError('a Comparator is required'); - } + if (stdio === undefined) { + return aliases.map(alias => opts[alias]); + } - var rangeTmp; + if (hasAlias(opts)) { + throw new Error(`It's not possible to provide \`stdio\` in combination with one of ${aliases.map(alias => `\`${alias}\``).join(', ')}`); + } - if (this.operator === '') { - rangeTmp = new Range(comp.value, loose); - return satisfies(this.value, rangeTmp, loose); - } else if (comp.operator === '') { - rangeTmp = new Range(this.value, loose); - return satisfies(comp.semver, rangeTmp, loose); - } + if (typeof stdio === 'string') { + return stdio; + } - var sameDirectionIncreasing = - (this.operator === '>=' || this.operator === '>') && - (comp.operator === '>=' || comp.operator === '>'); - var sameDirectionDecreasing = - (this.operator === '<=' || this.operator === '<') && - (comp.operator === '<=' || comp.operator === '<'); - var sameSemVer = this.semver.version === comp.semver.version; - var differentDirectionsInclusive = - (this.operator === '>=' || this.operator === '<=') && - (comp.operator === '>=' || comp.operator === '<='); - var oppositeDirectionsLessThan = - cmp(this.semver, '<', comp.semver, loose) && - ((this.operator === '>=' || this.operator === '>') && - (comp.operator === '<=' || comp.operator === '<')); - var oppositeDirectionsGreaterThan = - cmp(this.semver, '>', comp.semver, loose) && - ((this.operator === '<=' || this.operator === '<') && - (comp.operator === '>=' || comp.operator === '>')); + if (!Array.isArray(stdio)) { + throw new TypeError(`Expected \`stdio\` to be of type \`string\` or \`Array\`, got \`${typeof stdio}\``); + } - return sameDirectionIncreasing || sameDirectionDecreasing || - (sameSemVer && differentDirectionsInclusive) || - oppositeDirectionsLessThan || oppositeDirectionsGreaterThan; + const length = Math.max(stdio.length, aliases.length); + return Array.from({length}, (value, index) => stdio[index]); }; +module.exports = normalizeStdio; -exports.Range = Range; -function Range(range, loose) { - if (range instanceof Range) { - if (range.loose === loose) { - return range; - } else { - return new Range(range.raw, loose); - } - } +// `ipc` is pushed unless it is already present +module.exports.node = opts => { + const stdio = normalizeStdio(opts); - if (range instanceof Comparator) { - return new Range(range.value, loose); - } + if (stdio === 'ipc') { + return 'ipc'; + } - if (!(this instanceof Range)) - return new Range(range, loose); + if (stdio === undefined || typeof stdio === 'string') { + return [stdio, stdio, stdio, 'ipc']; + } - this.loose = loose; + if (stdio.includes('ipc')) { + return stdio; + } - // First, split based on boolean or || - this.raw = range; - this.set = range.split(/\s*\|\|\s*/).map(function(range) { - return this.parseRange(range.trim()); - }, this).filter(function(c) { - // throw out any that are not relevant for whatever reason - return c.length; - }); + return [...stdio, 'ipc']; +}; - if (!this.set.length) { - throw new TypeError('Invalid SemVer Range: ' + range); - } - this.format(); -} +/***/ }), +/* 147 */ +/***/ (function(module, exports, __webpack_require__) { -Range.prototype.format = function() { - this.range = this.set.map(function(comps) { - return comps.join(' ').trim(); - }).join('||').trim(); - return this.range; +"use strict"; + +const os = __webpack_require__(11); +const onExit = __webpack_require__(110); +const pFinally = __webpack_require__(148); + +const DEFAULT_FORCE_KILL_TIMEOUT = 1000 * 5; + +// Monkey-patches `childProcess.kill()` to add `forceKillAfterTimeout` behavior +const spawnedKill = (kill, signal = 'SIGTERM', options = {}) => { + const killResult = kill(signal); + setKillTimeout(kill, signal, options, killResult); + return killResult; }; -Range.prototype.toString = function() { - return this.range; +const setKillTimeout = (kill, signal, options, killResult) => { + if (!shouldForceKill(signal, options, killResult)) { + return; + } + + const timeout = getForceKillAfterTimeout(options); + setTimeout(() => { + kill('SIGKILL'); + }, timeout).unref(); }; -Range.prototype.parseRange = function(range) { - var loose = this.loose; - range = range.trim(); - debug('range', range, loose); - // `1.2.3 - 1.2.4` => `>=1.2.3 <=1.2.4` - var hr = loose ? re[HYPHENRANGELOOSE] : re[HYPHENRANGE]; - range = range.replace(hr, hyphenReplace); - debug('hyphen replace', range); - // `> 1.2.3 < 1.2.5` => `>1.2.3 <1.2.5` - range = range.replace(re[COMPARATORTRIM], comparatorTrimReplace); - debug('comparator trim', range, re[COMPARATORTRIM]); +const shouldForceKill = (signal, {forceKillAfterTimeout}, killResult) => { + return isSigterm(signal) && forceKillAfterTimeout !== false && killResult; +}; - // `~ 1.2.3` => `~1.2.3` - range = range.replace(re[TILDETRIM], tildeTrimReplace); +const isSigterm = signal => { + return signal === os.constants.signals.SIGTERM || + (typeof signal === 'string' && signal.toUpperCase() === 'SIGTERM'); +}; - // `^ 1.2.3` => `^1.2.3` - range = range.replace(re[CARETTRIM], caretTrimReplace); +const getForceKillAfterTimeout = ({forceKillAfterTimeout = true}) => { + if (forceKillAfterTimeout === true) { + return DEFAULT_FORCE_KILL_TIMEOUT; + } - // normalize spaces - range = range.split(/\s+/).join(' '); + if (!Number.isInteger(forceKillAfterTimeout) || forceKillAfterTimeout < 0) { + throw new TypeError(`Expected the \`forceKillAfterTimeout\` option to be a non-negative integer, got \`${forceKillAfterTimeout}\` (${typeof forceKillAfterTimeout})`); + } - // At this point, the range is completely trimmed and - // ready to be split into comparators. + return forceKillAfterTimeout; +}; - var compRe = loose ? re[COMPARATORLOOSE] : re[COMPARATOR]; - var set = range.split(' ').map(function(comp) { - return parseComparator(comp, loose); - }).join(' ').split(/\s+/); - if (this.loose) { - // in loose mode, throw out any that are not valid comparators - set = set.filter(function(comp) { - return !!comp.match(compRe); - }); - } - set = set.map(function(comp) { - return new Comparator(comp, loose); - }); +// `childProcess.cancel()` +const spawnedCancel = (spawned, context) => { + const killResult = spawned.kill(); - return set; + if (killResult) { + context.isCanceled = true; + } }; -Range.prototype.intersects = function(range, loose) { - if (!(range instanceof Range)) { - throw new TypeError('a Range is required'); - } +const timeoutKill = (spawned, signal, reject) => { + spawned.kill(signal); + reject(Object.assign(new Error('Timed out'), {timedOut: true, signal})); +}; - return this.set.some(function(thisComparators) { - return thisComparators.every(function(thisComparator) { - return range.set.some(function(rangeComparators) { - return rangeComparators.every(function(rangeComparator) { - return thisComparator.intersects(rangeComparator, loose); - }); - }); - }); - }); +// `timeout` option handling +const setupTimeout = (spawned, {timeout, killSignal = 'SIGTERM'}, spawnedPromise) => { + if (timeout === 0 || timeout === undefined) { + return spawnedPromise; + } + + if (!Number.isInteger(timeout) || timeout < 0) { + throw new TypeError(`Expected the \`timeout\` option to be a non-negative integer, got \`${timeout}\` (${typeof timeout})`); + } + + let timeoutId; + const timeoutPromise = new Promise((resolve, reject) => { + timeoutId = setTimeout(() => { + timeoutKill(spawned, killSignal, reject); + }, timeout); + }); + + const safeSpawnedPromise = pFinally(spawnedPromise, () => { + clearTimeout(timeoutId); + }); + + return Promise.race([timeoutPromise, safeSpawnedPromise]); }; -// Mostly just for testing and legacy API reasons -exports.toComparators = toComparators; -function toComparators(range, loose) { - return new Range(range, loose).set.map(function(comp) { - return comp.map(function(c) { - return c.value; - }).join(' ').trim().split(' '); - }); -} +// `cleanup` option handling +const setExitHandler = (spawned, {cleanup, detached}, timedPromise) => { + if (!cleanup || detached) { + return timedPromise; + } -// comprised of xranges, tildes, stars, and gtlt's at this point. -// already replaced the hyphen ranges -// turn into a set of JUST comparators. -function parseComparator(comp, loose) { - debug('comp', comp); - comp = replaceCarets(comp, loose); - debug('caret', comp); - comp = replaceTildes(comp, loose); - debug('tildes', comp); - comp = replaceXRanges(comp, loose); - debug('xrange', comp); - comp = replaceStars(comp, loose); - debug('stars', comp); - return comp; -} + const removeExitHandler = onExit(() => { + spawned.kill(); + }); -function isX(id) { - return !id || id.toLowerCase() === 'x' || id === '*'; -} + // TODO: Use native "finally" syntax when targeting Node.js 10 + return pFinally(timedPromise, removeExitHandler); +}; -// ~, ~> --> * (any, kinda silly) -// ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0 <3.0.0 -// ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0 <2.1.0 -// ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0 <1.3.0 -// ~1.2.3, ~>1.2.3 --> >=1.2.3 <1.3.0 -// ~1.2.0, ~>1.2.0 --> >=1.2.0 <1.3.0 -function replaceTildes(comp, loose) { - return comp.trim().split(/\s+/).map(function(comp) { - return replaceTilde(comp, loose); - }).join(' '); -} +module.exports = { + spawnedKill, + spawnedCancel, + setupTimeout, + setExitHandler +}; -function replaceTilde(comp, loose) { - var r = loose ? re[TILDELOOSE] : re[TILDE]; - return comp.replace(r, function(_, M, m, p, pr) { - debug('tilde', comp, _, M, m, p, pr); - var ret; - if (isX(M)) - ret = ''; - else if (isX(m)) - ret = '>=' + M + '.0.0 <' + (+M + 1) + '.0.0'; - else if (isX(p)) - // ~1.2 == >=1.2.0 <1.3.0 - ret = '>=' + M + '.' + m + '.0 <' + M + '.' + (+m + 1) + '.0'; - else if (pr) { - debug('replaceTilde pr', pr); - if (pr.charAt(0) !== '-') - pr = '-' + pr; - ret = '>=' + M + '.' + m + '.' + p + pr + - ' <' + M + '.' + (+m + 1) + '.0'; - } else - // ~1.2.3 == >=1.2.3 <1.3.0 - ret = '>=' + M + '.' + m + '.' + p + - ' <' + M + '.' + (+m + 1) + '.0'; +/***/ }), +/* 148 */ +/***/ (function(module, exports, __webpack_require__) { - debug('tilde return', ret); - return ret; - }); -} +"use strict"; -// ^ --> * (any, kinda silly) -// ^2, ^2.x, ^2.x.x --> >=2.0.0 <3.0.0 -// ^2.0, ^2.0.x --> >=2.0.0 <3.0.0 -// ^1.2, ^1.2.x --> >=1.2.0 <2.0.0 -// ^1.2.3 --> >=1.2.3 <2.0.0 -// ^1.2.0 --> >=1.2.0 <2.0.0 -function replaceCarets(comp, loose) { - return comp.trim().split(/\s+/).map(function(comp) { - return replaceCaret(comp, loose); - }).join(' '); -} -function replaceCaret(comp, loose) { - debug('caret', comp, loose); - var r = loose ? re[CARETLOOSE] : re[CARET]; - return comp.replace(r, function(_, M, m, p, pr) { - debug('caret', comp, _, M, m, p, pr); - var ret; +module.exports = async ( + promise, + onFinally = (() => {}) +) => { + let value; + try { + value = await promise; + } catch (error) { + await onFinally(); + throw error; + } - if (isX(M)) - ret = ''; - else if (isX(m)) - ret = '>=' + M + '.0.0 <' + (+M + 1) + '.0.0'; - else if (isX(p)) { - if (M === '0') - ret = '>=' + M + '.' + m + '.0 <' + M + '.' + (+m + 1) + '.0'; - else - ret = '>=' + M + '.' + m + '.0 <' + (+M + 1) + '.0.0'; - } else if (pr) { - debug('replaceCaret pr', pr); - if (pr.charAt(0) !== '-') - pr = '-' + pr; - if (M === '0') { - if (m === '0') - ret = '>=' + M + '.' + m + '.' + p + pr + - ' <' + M + '.' + m + '.' + (+p + 1); - else - ret = '>=' + M + '.' + m + '.' + p + pr + - ' <' + M + '.' + (+m + 1) + '.0'; - } else - ret = '>=' + M + '.' + m + '.' + p + pr + - ' <' + (+M + 1) + '.0.0'; - } else { - debug('no pr'); - if (M === '0') { - if (m === '0') - ret = '>=' + M + '.' + m + '.' + p + - ' <' + M + '.' + m + '.' + (+p + 1); - else - ret = '>=' + M + '.' + m + '.' + p + - ' <' + M + '.' + (+m + 1) + '.0'; - } else - ret = '>=' + M + '.' + m + '.' + p + - ' <' + (+M + 1) + '.0.0'; - } + await onFinally(); + return value; +}; - debug('caret return', ret); - return ret; - }); -} -function replaceXRanges(comp, loose) { - debug('replaceXRanges', comp, loose); - return comp.split(/\s+/).map(function(comp) { - return replaceXRange(comp, loose); - }).join(' '); -} +/***/ }), +/* 149 */ +/***/ (function(module, exports, __webpack_require__) { -function replaceXRange(comp, loose) { - comp = comp.trim(); - var r = loose ? re[XRANGELOOSE] : re[XRANGE]; - return comp.replace(r, function(ret, gtlt, M, m, p, pr) { - debug('xRange', comp, ret, gtlt, M, m, p, pr); - var xM = isX(M); - var xm = xM || isX(m); - var xp = xm || isX(p); - var anyX = xp; +"use strict"; - if (gtlt === '=' && anyX) - gtlt = ''; +const isStream = __webpack_require__(150); +const getStream = __webpack_require__(151); +const mergeStream = __webpack_require__(155); - if (xM) { - if (gtlt === '>' || gtlt === '<') { - // nothing is allowed - ret = '<0.0.0'; - } else { - // nothing is forbidden - ret = '*'; - } - } else if (gtlt && anyX) { - // replace X with 0 - if (xm) - m = 0; - if (xp) - p = 0; +// `input` option +const handleInput = (spawned, input) => { + // Checking for stdin is workaround for https://github.com/nodejs/node/issues/26852 + // TODO: Remove `|| spawned.stdin === undefined` once we drop support for Node.js <=12.2.0 + if (input === undefined || spawned.stdin === undefined) { + return; + } - if (gtlt === '>') { - // >1 => >=2.0.0 - // >1.2 => >=1.3.0 - // >1.2.3 => >= 1.2.4 - gtlt = '>='; - if (xm) { - M = +M + 1; - m = 0; - p = 0; - } else if (xp) { - m = +m + 1; - p = 0; - } - } else if (gtlt === '<=') { - // <=0.7.x is actually <0.8.0, since any 0.7.x should - // pass. Similarly, <=7.x is actually <8.0.0, etc. - gtlt = '<'; - if (xm) - M = +M + 1; - else - m = +m + 1; - } + if (isStream(input)) { + input.pipe(spawned.stdin); + } else { + spawned.stdin.end(input); + } +}; - ret = gtlt + M + '.' + m + '.' + p; - } else if (xm) { - ret = '>=' + M + '.0.0 <' + (+M + 1) + '.0.0'; - } else if (xp) { - ret = '>=' + M + '.' + m + '.0 <' + M + '.' + (+m + 1) + '.0'; - } +// `all` interleaves `stdout` and `stderr` +const makeAllStream = (spawned, {all}) => { + if (!all || (!spawned.stdout && !spawned.stderr)) { + return; + } - debug('xRange return', ret); + const mixed = mergeStream(); - return ret; - }); -} + if (spawned.stdout) { + mixed.add(spawned.stdout); + } -// Because * is AND-ed with everything else in the comparator, -// and '' means "any version", just remove the *s entirely. -function replaceStars(comp, loose) { - debug('replaceStars', comp, loose); - // Looseness is ignored here. star is always as loose as it gets! - return comp.trim().replace(re[STAR], ''); -} + if (spawned.stderr) { + mixed.add(spawned.stderr); + } -// This function is passed to string.replace(re[HYPHENRANGE]) -// M, m, patch, prerelease, build -// 1.2 - 3.4.5 => >=1.2.0 <=3.4.5 -// 1.2.3 - 3.4 => >=1.2.0 <3.5.0 Any 3.4.x will do -// 1.2 - 3.4 => >=1.2.0 <3.5.0 -function hyphenReplace($0, - from, fM, fm, fp, fpr, fb, - to, tM, tm, tp, tpr, tb) { + return mixed; +}; - if (isX(fM)) - from = ''; - else if (isX(fm)) - from = '>=' + fM + '.0.0'; - else if (isX(fp)) - from = '>=' + fM + '.' + fm + '.0'; - else - from = '>=' + from; +// On failure, `result.stdout|stderr|all` should contain the currently buffered stream +const getBufferedData = async (stream, streamPromise) => { + if (!stream) { + return; + } - if (isX(tM)) - to = ''; - else if (isX(tm)) - to = '<' + (+tM + 1) + '.0.0'; - else if (isX(tp)) - to = '<' + tM + '.' + (+tm + 1) + '.0'; - else if (tpr) - to = '<=' + tM + '.' + tm + '.' + tp + '-' + tpr; - else - to = '<=' + to; + stream.destroy(); - return (from + ' ' + to).trim(); -} + try { + return await streamPromise; + } catch (error) { + return error.bufferedData; + } +}; +const getStreamPromise = (stream, {encoding, buffer, maxBuffer}) => { + if (!stream || !buffer) { + return; + } -// if ANY of the sets match ALL of its comparators, then pass -Range.prototype.test = function(version) { - if (!version) - return false; + if (encoding) { + return getStream(stream, {encoding, maxBuffer}); + } - if (typeof version === 'string') - version = new SemVer(version, this.loose); + return getStream.buffer(stream, {maxBuffer}); +}; - for (var i = 0; i < this.set.length; i++) { - if (testSet(this.set[i], version)) - return true; - } - return false; +// Retrieve result of child process: exit code, signal, error, streams (stdout/stderr/all) +const getSpawnedResult = async ({stdout, stderr, all}, {encoding, buffer, maxBuffer}, processDone) => { + const stdoutPromise = getStreamPromise(stdout, {encoding, buffer, maxBuffer}); + const stderrPromise = getStreamPromise(stderr, {encoding, buffer, maxBuffer}); + const allPromise = getStreamPromise(all, {encoding, buffer, maxBuffer: maxBuffer * 2}); + + try { + return await Promise.all([processDone, stdoutPromise, stderrPromise, allPromise]); + } catch (error) { + return Promise.all([ + {error, signal: error.signal, timedOut: error.timedOut}, + getBufferedData(stdout, stdoutPromise), + getBufferedData(stderr, stderrPromise), + getBufferedData(all, allPromise) + ]); + } }; -function testSet(set, version) { - for (var i = 0; i < set.length; i++) { - if (!set[i].test(version)) - return false; - } +const validateInputSync = ({input}) => { + if (isStream(input)) { + throw new TypeError('The `input` option cannot be a stream in sync mode'); + } +}; - if (version.prerelease.length) { - // Find the set of versions that are allowed to have prereleases - // For example, ^1.2.3-pr.1 desugars to >=1.2.3-pr.1 <2.0.0 - // That should allow `1.2.3-pr.2` to pass. - // However, `1.2.4-alpha.notready` should NOT be allowed, - // even though it's within the range set by the comparators. - for (var i = 0; i < set.length; i++) { - debug(set[i].semver); - if (set[i].semver === ANY) - continue; +module.exports = { + handleInput, + makeAllStream, + getSpawnedResult, + validateInputSync +}; - if (set[i].semver.prerelease.length > 0) { - var allowed = set[i].semver; - if (allowed.major === version.major && - allowed.minor === version.minor && - allowed.patch === version.patch) - return true; - } - } - // Version has a -pre, but it's not one of the ones we like. - return false; - } - return true; -} +/***/ }), +/* 150 */ +/***/ (function(module, exports, __webpack_require__) { -exports.satisfies = satisfies; -function satisfies(version, range, loose) { - try { - range = new Range(range, loose); - } catch (er) { - return false; - } - return range.test(version); -} +"use strict"; -exports.maxSatisfying = maxSatisfying; -function maxSatisfying(versions, range, loose) { - var max = null; - var maxSV = null; - try { - var rangeObj = new Range(range, loose); - } catch (er) { - return null; - } - versions.forEach(function (v) { - if (rangeObj.test(v)) { // satisfies(v, range, loose) - if (!max || maxSV.compare(v) === -1) { // compare(max, v, true) - max = v; - maxSV = new SemVer(max, loose); - } - } - }) - return max; -} -exports.minSatisfying = minSatisfying; -function minSatisfying(versions, range, loose) { - var min = null; - var minSV = null; - try { - var rangeObj = new Range(range, loose); - } catch (er) { - return null; - } - versions.forEach(function (v) { - if (rangeObj.test(v)) { // satisfies(v, range, loose) - if (!min || minSV.compare(v) === 1) { // compare(min, v, true) - min = v; - minSV = new SemVer(min, loose); - } - } - }) - return min; -} +const isStream = stream => + stream !== null && + typeof stream === 'object' && + typeof stream.pipe === 'function'; -exports.validRange = validRange; -function validRange(range, loose) { - try { - // Return '*' instead of '' so that truthiness works. - // This will throw if it's invalid anyway - return new Range(range, loose).range || '*'; - } catch (er) { - return null; - } -} +isStream.writable = stream => + isStream(stream) && + stream.writable !== false && + typeof stream._write === 'function' && + typeof stream._writableState === 'object'; -// Determine if version is less than all the versions possible in the range -exports.ltr = ltr; -function ltr(version, range, loose) { - return outside(version, range, '<', loose); -} +isStream.readable = stream => + isStream(stream) && + stream.readable !== false && + typeof stream._read === 'function' && + typeof stream._readableState === 'object'; -// Determine if version is greater than all the versions possible in the range. -exports.gtr = gtr; -function gtr(version, range, loose) { - return outside(version, range, '>', loose); -} +isStream.duplex = stream => + isStream.writable(stream) && + isStream.readable(stream); -exports.outside = outside; -function outside(version, range, hilo, loose) { - version = new SemVer(version, loose); - range = new Range(range, loose); +isStream.transform = stream => + isStream.duplex(stream) && + typeof stream._transform === 'function' && + typeof stream._transformState === 'object'; - var gtfn, ltefn, ltfn, comp, ecomp; - switch (hilo) { - case '>': - gtfn = gt; - ltefn = lte; - ltfn = lt; - comp = '>'; - ecomp = '>='; - break; - case '<': - gtfn = lt; - ltefn = gte; - ltfn = gt; - comp = '<'; - ecomp = '<='; - break; - default: - throw new TypeError('Must provide a hilo val of "<" or ">"'); - } +module.exports = isStream; - // If it satisifes the range it is not outside - if (satisfies(version, range, loose)) { - return false; - } - // From now on, variable terms are as if we're in "gtr" mode. - // but note that everything is flipped for the "ltr" function. +/***/ }), +/* 151 */ +/***/ (function(module, exports, __webpack_require__) { - for (var i = 0; i < range.set.length; ++i) { - var comparators = range.set[i]; +"use strict"; - var high = null; - var low = null; - - comparators.forEach(function(comparator) { - if (comparator.semver === ANY) { - comparator = new Comparator('>=0.0.0') - } - high = high || comparator; - low = low || comparator; - if (gtfn(comparator.semver, high.semver, loose)) { - high = comparator; - } else if (ltfn(comparator.semver, low.semver, loose)) { - low = comparator; - } - }); - - // If the edge version comparator has a operator then our version - // isn't outside it - if (high.operator === comp || high.operator === ecomp) { - return false; - } - - // If the lowest version comparator has an operator and our version - // is less than it then it isn't higher than the range - if ((!low.operator || low.operator === comp) && - ltefn(version, low.semver)) { - return false; - } else if (low.operator === ecomp && ltfn(version, low.semver)) { - return false; - } - } - return true; -} - -exports.prerelease = prerelease; -function prerelease(version, loose) { - var parsed = parse(version, loose); - return (parsed && parsed.prerelease.length) ? parsed.prerelease : null; -} - -exports.intersects = intersects; -function intersects(r1, r2, loose) { - r1 = new Range(r1, loose) - r2 = new Range(r2, loose) - return r1.intersects(r2) -} - -exports.coerce = coerce; -function coerce(version) { - if (version instanceof SemVer) - return version; - - if (typeof version !== 'string') - return null; - - var match = version.match(re[COERCE]); - - if (match == null) - return null; - - return parse((match[1] || '0') + '.' + (match[2] || '0') + '.' + (match[3] || '0')); -} - - -/***/ }), -/* 138 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -const isWin = process.platform === 'win32'; - -function notFoundError(original, syscall) { - return Object.assign(new Error(`${syscall} ${original.command} ENOENT`), { - code: 'ENOENT', - errno: 'ENOENT', - syscall: `${syscall} ${original.command}`, - path: original.command, - spawnargs: original.args, - }); -} - -function hookChildProcess(cp, parsed) { - if (!isWin) { - return; - } - - const originalEmit = cp.emit; - - cp.emit = function (name, arg1) { - // If emitting "exit" event and exit code is 1, we need to check if - // the command exists and emit an "error" instead - // See https://github.com/IndigoUnited/node-cross-spawn/issues/16 - if (name === 'exit') { - const err = verifyENOENT(arg1, parsed, 'spawn'); - - if (err) { - return originalEmit.call(cp, 'error', err); - } - } - - return originalEmit.apply(cp, arguments); // eslint-disable-line prefer-rest-params - }; -} - -function verifyENOENT(status, parsed) { - if (isWin && status === 1 && !parsed.file) { - return notFoundError(parsed.original, 'spawn'); - } - - return null; -} - -function verifyENOENTSync(status, parsed) { - if (isWin && status === 1 && !parsed.file) { - return notFoundError(parsed.original, 'spawnSync'); - } - - return null; -} - -module.exports = { - hookChildProcess, - verifyENOENT, - verifyENOENTSync, - notFoundError, -}; - - -/***/ }), -/* 139 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -module.exports = function (x) { - var lf = typeof x === 'string' ? '\n' : '\n'.charCodeAt(); - var cr = typeof x === 'string' ? '\r' : '\r'.charCodeAt(); - - if (x[x.length - 1] === lf) { - x = x.slice(0, x.length - 1); - } - - if (x[x.length - 1] === cr) { - x = x.slice(0, x.length - 1); - } - - return x; -}; - - -/***/ }), -/* 140 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -const path = __webpack_require__(16); -const pathKey = __webpack_require__(141); - -module.exports = opts => { - opts = Object.assign({ - cwd: process.cwd(), - path: process.env[pathKey()] - }, opts); - - let prev; - let pth = path.resolve(opts.cwd); - const ret = []; - - while (prev !== pth) { - ret.push(path.join(pth, 'node_modules/.bin')); - prev = pth; - pth = path.resolve(pth, '..'); - } - - // ensure the running `node` binary is used - ret.push(path.dirname(process.execPath)); - - return ret.concat(opts.path).join(path.delimiter); -}; - -module.exports.env = opts => { - opts = Object.assign({ - env: process.env - }, opts); - - const env = Object.assign({}, opts.env); - const path = pathKey({env}); - - opts.path = env[path]; - env[path] = module.exports(opts); - - return env; -}; - - -/***/ }), -/* 141 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -module.exports = opts => { - opts = opts || {}; - - const env = opts.env || process.env; - const platform = opts.platform || process.platform; - - if (platform !== 'win32') { - return 'PATH'; - } - - return Object.keys(env).find(x => x.toUpperCase() === 'PATH') || 'Path'; -}; - - -/***/ }), -/* 142 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var isStream = module.exports = function (stream) { - return stream !== null && typeof stream === 'object' && typeof stream.pipe === 'function'; -}; - -isStream.writable = function (stream) { - return isStream(stream) && stream.writable !== false && typeof stream._write === 'function' && typeof stream._writableState === 'object'; -}; - -isStream.readable = function (stream) { - return isStream(stream) && stream.readable !== false && typeof stream._read === 'function' && typeof stream._readableState === 'object'; -}; - -isStream.duplex = function (stream) { - return isStream.writable(stream) && isStream.readable(stream); -}; - -isStream.transform = function (stream) { - return isStream.duplex(stream) && typeof stream._transform === 'function' && typeof stream._transformState === 'object'; -}; - - -/***/ }), -/* 143 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -const pump = __webpack_require__(144); -const bufferStream = __webpack_require__(146); +const pump = __webpack_require__(152); +const bufferStream = __webpack_require__(154); class MaxBufferError extends Error { constructor() { @@ -21745,21 +21130,25 @@ class MaxBufferError extends Error { } } -function getStream(inputStream, options) { +async function getStream(inputStream, options) { if (!inputStream) { return Promise.reject(new Error('Expected a stream')); } - options = Object.assign({maxBuffer: Infinity}, options); + options = { + maxBuffer: Infinity, + ...options + }; const {maxBuffer} = options; let stream; - return new Promise((resolve, reject) => { + await new Promise((resolve, reject) => { const rejectPromise = error => { if (error) { // A null check error.bufferedData = stream.getBufferedValue(); } + reject(error); }; @@ -21777,21 +21166,25 @@ function getStream(inputStream, options) { rejectPromise(new MaxBufferError()); } }); - }).then(() => stream.getBufferedValue()); + }); + + return stream.getBufferedValue(); } module.exports = getStream; -module.exports.buffer = (stream, options) => getStream(stream, Object.assign({}, options, {encoding: 'buffer'})); -module.exports.array = (stream, options) => getStream(stream, Object.assign({}, options, {array: true})); +// TODO: Remove this for the next major release +module.exports.default = getStream; +module.exports.buffer = (stream, options) => getStream(stream, {...options, encoding: 'buffer'}); +module.exports.array = (stream, options) => getStream(stream, {...options, array: true}); module.exports.MaxBufferError = MaxBufferError; /***/ }), -/* 144 */ +/* 152 */ /***/ (function(module, exports, __webpack_require__) { var once = __webpack_require__(52) -var eos = __webpack_require__(145) +var eos = __webpack_require__(153) var fs = __webpack_require__(23) // we only need fs to get the ReadStream and WriteStream prototypes var noop = function () {} @@ -21875,7 +21268,7 @@ module.exports = pump /***/ }), -/* 145 */ +/* 153 */ /***/ (function(module, exports, __webpack_require__) { var once = __webpack_require__(52); @@ -21968,186 +21361,223 @@ module.exports = eos; /***/ }), -/* 146 */ +/* 154 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const {PassThrough} = __webpack_require__(28); +const {PassThrough: PassThroughStream} = __webpack_require__(28); module.exports = options => { - options = Object.assign({}, options); + options = {...options}; const {array} = options; let {encoding} = options; - const buffer = encoding === 'buffer'; + const isBuffer = encoding === 'buffer'; let objectMode = false; if (array) { - objectMode = !(encoding || buffer); + objectMode = !(encoding || isBuffer); } else { encoding = encoding || 'utf8'; } - if (buffer) { + if (isBuffer) { encoding = null; } - let len = 0; - const ret = []; - const stream = new PassThrough({objectMode}); + const stream = new PassThroughStream({objectMode}); if (encoding) { stream.setEncoding(encoding); } + let length = 0; + const chunks = []; + stream.on('data', chunk => { - ret.push(chunk); + chunks.push(chunk); if (objectMode) { - len = ret.length; + length = chunks.length; } else { - len += chunk.length; + length += chunk.length; } }); stream.getBufferedValue = () => { if (array) { - return ret; + return chunks; } - return buffer ? Buffer.concat(ret, len) : ret.join(''); + return isBuffer ? Buffer.concat(chunks, length) : chunks.join(''); }; - stream.getBufferedLength = () => len; + stream.getBufferedLength = () => length; return stream; }; /***/ }), -/* 147 */ +/* 155 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -module.exports = (promise, onFinally) => { - onFinally = onFinally || (() => {}); - return promise.then( - val => new Promise(resolve => { - resolve(onFinally()); - }).then(() => val), - err => new Promise(resolve => { - resolve(onFinally()); - }).then(() => { - throw err; - }) - ); -}; +const { PassThrough } = __webpack_require__(28); + +module.exports = function (/*streams...*/) { + var sources = [] + var output = new PassThrough({objectMode: true}) + + output.setMaxListeners(0) + + output.add = add + output.isEmpty = isEmpty + + output.on('unpipe', remove) + + Array.prototype.slice.call(arguments).forEach(add) + + return output + + function add (source) { + if (Array.isArray(source)) { + source.forEach(add) + return this + } + + sources.push(source); + source.once('end', remove.bind(null, source)) + source.once('error', output.emit.bind(output, 'error')) + source.pipe(output, {end: false}) + return this + } + + function isEmpty () { + return sources.length == 0; + } + + function remove (source) { + sources = sources.filter(function (it) { return it !== source }) + if (!sources.length && output.readable) { output.end() } + } +} /***/ }), -/* 148 */ +/* 156 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -// Older verions of Node.js might not have `util.getSystemErrorName()`. -// In that case, fall back to a deprecated internal. -const util = __webpack_require__(29); - -let uv; +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 + }); +}; -if (typeof util.getSystemErrorName === 'function') { - module.exports = util.getSystemErrorName; -} else { - try { - uv = process.binding('uv'); +// The return value is a mixin of `childProcess` and `Promise` +const mergePromise = (spawned, promise) => { + mergePromiseProperty(spawned, promise, 'then'); + mergePromiseProperty(spawned, promise, 'catch'); - if (typeof uv.errname !== 'function') { - throw new TypeError('uv.errname is not a function'); - } - } catch (err) { - console.error('execa/lib/errname: unable to establish process.binding(\'uv\')', err); - uv = null; + // TODO: Remove the `if`-guard when targeting Node.js 10 + if (Promise.prototype.finally) { + mergePromiseProperty(spawned, promise, 'finally'); } - module.exports = code => errname(uv, code); -} + return spawned; +}; -// Used for testing the fallback behavior -module.exports.__test__ = errname; +// Use promises instead of `child_process` events +const getSpawnedPromise = spawned => { + return new Promise((resolve, reject) => { + spawned.on('exit', (exitCode, signal) => { + resolve({exitCode, signal}); + }); -function errname(uv, code) { - if (uv) { - return uv.errname(code); - } + spawned.on('error', error => { + reject(error); + }); - if (!(code < 0)) { - throw new Error('err >= 0'); - } + if (spawned.stdin) { + spawned.stdin.on('error', error => { + reject(error); + }); + } + }); +}; - return `Unknown system error ${code}`; -} +module.exports = { + mergePromise, + getSpawnedPromise +}; /***/ }), -/* 149 */ +/* 157 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const alias = ['stdin', 'stdout', 'stderr']; - -const hasAlias = opts => alias.some(x => Boolean(opts[x])); +const SPACES_REGEXP = / +/g; -module.exports = opts => { - if (!opts) { - return null; +const joinCommand = (file, args = []) => { + if (!Array.isArray(args)) { + return file; } - if (opts.stdio && hasAlias(opts)) { - throw new Error(`It's not possible to provide \`stdio\` in combination with one of ${alias.map(x => `\`${x}\``).join(', ')}`); - } + return [file, ...args].join(' '); +}; - if (typeof opts.stdio === 'string') { - return opts.stdio; +// Allow spaces to be escaped by a backslash if not meant as a delimiter +const handleEscaping = (tokens, token, index) => { + if (index === 0) { + return [token]; } - const stdio = opts.stdio || []; + const previousToken = tokens[tokens.length - 1]; - if (!Array.isArray(stdio)) { - throw new TypeError(`Expected \`stdio\` to be of type \`string\` or \`Array\`, got \`${typeof stdio}\``); + if (previousToken.endsWith('\\')) { + return [...tokens.slice(0, -1), `${previousToken.slice(0, -1)} ${token}`]; } - const result = []; - const len = Math.max(stdio.length, alias.length); - - for (let i = 0; i < len; i++) { - let value = null; - - if (stdio[i] !== undefined) { - value = stdio[i]; - } else if (opts[alias[i]] !== undefined) { - value = opts[alias[i]]; - } + return [...tokens, token]; +}; - result[i] = value; - } +// Handle `execa.command()` +const parseCommand = command => { + return command + .trim() + .split(SPACES_REGEXP) + .reduce(handleEscaping, []); +}; - return result; +module.exports = { + joinCommand, + parseCommand }; /***/ }), -/* 150 */ +/* 158 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const chalk = __webpack_require__(151); +const chalk = __webpack_require__(159); const isSupported = process.platform !== 'win32' || process.env.CI || process.env.TERM === 'xterm-256color'; @@ -22169,16 +21599,16 @@ module.exports = isSupported ? main : fallbacks; /***/ }), -/* 151 */ +/* 159 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const escapeStringRegexp = __webpack_require__(3); -const ansiStyles = __webpack_require__(152); -const stdoutColor = __webpack_require__(153).stdout; +const ansiStyles = __webpack_require__(160); +const stdoutColor = __webpack_require__(161).stdout; -const template = __webpack_require__(154); +const template = __webpack_require__(162); const isSimpleWindowsTerm = process.platform === 'win32' && !(process.env.TERM || '').toLowerCase().startsWith('xterm'); @@ -22404,7 +21834,7 @@ module.exports.default = module.exports; // For TypeScript /***/ }), -/* 152 */ +/* 160 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -22577,7 +22007,7 @@ Object.defineProperty(module, 'exports', { /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(5)(module))) /***/ }), -/* 153 */ +/* 161 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -22719,7 +22149,7 @@ module.exports = { /***/ }), -/* 154 */ +/* 162 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -22854,7 +22284,7 @@ module.exports = (chalk, tmp) => { /***/ }), -/* 155 */ +/* 163 */ /***/ (function(module, exports, __webpack_require__) { // Copyright IBM Corp. 2014,2018. All Rights Reserved. @@ -22862,12 +22292,12 @@ module.exports = (chalk, tmp) => { // This file is licensed under the Apache License 2.0. // License text available at https://opensource.org/licenses/Apache-2.0 -module.exports = __webpack_require__(156); -module.exports.cli = __webpack_require__(160); +module.exports = __webpack_require__(164); +module.exports.cli = __webpack_require__(168); /***/ }), -/* 156 */ +/* 164 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -22882,9 +22312,9 @@ var stream = __webpack_require__(28); var util = __webpack_require__(29); var fs = __webpack_require__(23); -var through = __webpack_require__(157); -var duplexer = __webpack_require__(158); -var StringDecoder = __webpack_require__(159).StringDecoder; +var through = __webpack_require__(165); +var duplexer = __webpack_require__(166); +var StringDecoder = __webpack_require__(167).StringDecoder; module.exports = Logger; @@ -23073,7 +22503,7 @@ function lineMerger(host) { /***/ }), -/* 157 */ +/* 165 */ /***/ (function(module, exports, __webpack_require__) { var Stream = __webpack_require__(28) @@ -23187,7 +22617,7 @@ function through (write, end, opts) { /***/ }), -/* 158 */ +/* 166 */ /***/ (function(module, exports, __webpack_require__) { var Stream = __webpack_require__(28) @@ -23280,13 +22710,13 @@ function duplex(writer, reader) { /***/ }), -/* 159 */ +/* 167 */ /***/ (function(module, exports) { module.exports = require("string_decoder"); /***/ }), -/* 160 */ +/* 168 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -23297,11 +22727,11 @@ module.exports = require("string_decoder"); -var minimist = __webpack_require__(161); +var minimist = __webpack_require__(169); var path = __webpack_require__(16); -var Logger = __webpack_require__(156); -var pkg = __webpack_require__(162); +var Logger = __webpack_require__(164); +var pkg = __webpack_require__(170); module.exports = cli; @@ -23355,7 +22785,7 @@ function usage($0, p) { /***/ }), -/* 161 */ +/* 169 */ /***/ (function(module, exports) { module.exports = function (args, opts) { @@ -23584,1218 +23014,8489 @@ function hasKey (obj, keys) { o = (o[key] || {}); }); - var key = keys[keys.length - 1]; - return key in o; -} + var key = keys[keys.length - 1]; + return key in o; +} + +function isNumber (x) { + if (typeof x === 'number') return true; + if (/^0x[0-9a-f]+$/i.test(x)) return true; + return /^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(e[-+]?\d+)?$/.test(x); +} + + + +/***/ }), +/* 170 */ +/***/ (function(module) { + +module.exports = JSON.parse("{\"name\":\"strong-log-transformer\",\"version\":\"2.1.0\",\"description\":\"Stream transformer that prefixes lines with timestamps and other things.\",\"author\":\"Ryan Graham \",\"license\":\"Apache-2.0\",\"repository\":{\"type\":\"git\",\"url\":\"git://github.com/strongloop/strong-log-transformer\"},\"keywords\":[\"logging\",\"streams\"],\"bugs\":{\"url\":\"https://github.com/strongloop/strong-log-transformer/issues\"},\"homepage\":\"https://github.com/strongloop/strong-log-transformer\",\"directories\":{\"test\":\"test\"},\"bin\":{\"sl-log-transformer\":\"bin/sl-log-transformer.js\"},\"main\":\"index.js\",\"scripts\":{\"test\":\"tap --100 test/test-*\"},\"dependencies\":{\"duplexer\":\"^0.1.1\",\"minimist\":\"^1.2.0\",\"through\":\"^2.3.4\"},\"devDependencies\":{\"tap\":\"^12.0.1\"},\"engines\":{\"node\":\">=4\"}}"); + +/***/ }), +/* 171 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "workspacePackagePaths", function() { return workspacePackagePaths; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "copyWorkspacePackages", function() { return copyWorkspacePackages; }); +/* harmony import */ var glob__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(37); +/* harmony import */ var glob__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(glob__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(16); +/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_1__); +/* harmony import */ var util__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(29); +/* harmony import */ var util__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(util__WEBPACK_IMPORTED_MODULE_2__); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(172); +/* harmony import */ var _fs__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(20); +/* harmony import */ var _package_json__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(55); +/* harmony import */ var _projects__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(36); +/* + * 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 glob = Object(util__WEBPACK_IMPORTED_MODULE_2__["promisify"])(glob__WEBPACK_IMPORTED_MODULE_0___default.a); +async function workspacePackagePaths(rootPath) { + const rootPkgJson = await Object(_package_json__WEBPACK_IMPORTED_MODULE_5__["readPackageJson"])(rootPath); + + if (!rootPkgJson.workspaces) { + return []; + } + + const workspacesPathsPatterns = rootPkgJson.workspaces.packages; + let workspaceProjectsPaths = []; + + for (const pattern of workspacesPathsPatterns) { + workspaceProjectsPaths = workspaceProjectsPaths.concat((await packagesFromGlobPattern({ + pattern, + rootPath + }))); + } // Filter out exclude glob patterns + + + for (const pattern of workspacesPathsPatterns) { + if (pattern.startsWith('!')) { + const pathToRemove = path__WEBPACK_IMPORTED_MODULE_1___default.a.join(rootPath, pattern.slice(1), 'package.json'); + workspaceProjectsPaths = workspaceProjectsPaths.filter(p => p !== pathToRemove); + } + } + + return workspaceProjectsPaths; +} +async function copyWorkspacePackages(rootPath) { + const projectPaths = Object(_config__WEBPACK_IMPORTED_MODULE_3__["getProjectPaths"])(rootPath, {}); + const projects = await Object(_projects__WEBPACK_IMPORTED_MODULE_6__["getProjects"])(rootPath, projectPaths); + + for (const project of projects.values()) { + const dest = path__WEBPACK_IMPORTED_MODULE_1___default.a.resolve(rootPath, 'node_modules', project.name); + + if ((await Object(_fs__WEBPACK_IMPORTED_MODULE_4__["isSymlink"])(dest)) === false) { + continue; + } // Remove the symlink + + + await Object(_fs__WEBPACK_IMPORTED_MODULE_4__["unlink"])(dest); // Copy in the package + + await Object(_fs__WEBPACK_IMPORTED_MODULE_4__["copyDirectory"])(project.path, dest); + } +} + +function packagesFromGlobPattern({ + pattern, + rootPath +}) { + const globOptions = { + cwd: rootPath, + // Should throw in case of unusual errors when reading the file system + strict: true, + // Always returns absolute paths for matched files + absolute: true, + // Do not match ** against multiple filenames + // (This is only specified because we currently don't have a need for it.) + noglobstar: true + }; + return glob(path__WEBPACK_IMPORTED_MODULE_1___default.a.join(pattern, 'package.json'), globOptions); +} + +/***/ }), +/* 172 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "getProjectPaths", function() { return getProjectPaths; }); +/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(16); +/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_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. + */ + + +/** + * Returns all the paths where plugins are located + */ +function getProjectPaths(rootPath, options = {}) { + const skipKibanaPlugins = Boolean(options['skip-kibana-plugins']); + const ossOnly = Boolean(options.oss); + const projectPaths = [rootPath, Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'packages/*')]; // This is needed in order to install the dependencies for the declared + // plugin functional used in the selenium functional tests. + // As we are now using the webpack dll for the client vendors dependencies + // when we run the plugin functional tests against the distributable + // dependencies used by such plugins like @eui, react and react-dom can't + // be loaded from the dll as the context is different from the one declared + // into the webpack dll reference plugin. + // In anyway, have a plugin declaring their own dependencies is the + // correct and the expect behavior. + + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'test/plugin_functional/plugins/*')); + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'test/interpreter_functional/plugins/*')); + + if (!ossOnly) { + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'x-pack')); + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'x-pack/legacy/plugins/*')); + } + + if (!skipKibanaPlugins) { + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, '../kibana-extra/*')); + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, '../kibana-extra/*/packages/*')); + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, '../kibana-extra/*/plugins/*')); + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'plugins/*')); + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'plugins/*/packages/*')); + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'plugins/*/plugins/*')); + } + + return projectPaths; +} + +/***/ }), +/* 173 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CleanCommand", function() { return CleanCommand; }); +/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2); +/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(chalk__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(174); +/* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(del__WEBPACK_IMPORTED_MODULE_1__); +/* harmony import */ var ora__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(261); +/* harmony import */ var ora__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(ora__WEBPACK_IMPORTED_MODULE_2__); +/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(16); +/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_3__); +/* harmony import */ var _utils_fs__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(20); +/* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(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. + */ + + + + + + +const CleanCommand = { + description: 'Remove the node_modules and target directories from all projects.', + name: 'clean', + + async run(projects) { + const toDelete = []; + + for (const project of projects.values()) { + if (await Object(_utils_fs__WEBPACK_IMPORTED_MODULE_4__["isDirectory"])(project.nodeModulesLocation)) { + toDelete.push({ + cwd: project.path, + pattern: Object(path__WEBPACK_IMPORTED_MODULE_3__["relative"])(project.path, project.nodeModulesLocation) + }); + } + + if (await Object(_utils_fs__WEBPACK_IMPORTED_MODULE_4__["isDirectory"])(project.targetLocation)) { + toDelete.push({ + cwd: project.path, + pattern: Object(path__WEBPACK_IMPORTED_MODULE_3__["relative"])(project.path, project.targetLocation) + }); + } + + const { + extraPatterns + } = project.getCleanConfig(); + + if (extraPatterns) { + toDelete.push({ + cwd: project.path, + pattern: extraPatterns + }); + } + } + + if (toDelete.length === 0) { + _utils_log__WEBPACK_IMPORTED_MODULE_5__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.bold.green('\n\nNothing to delete')); + } else { + _utils_log__WEBPACK_IMPORTED_MODULE_5__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.bold.red('\n\nDeleting:\n')); + /** + * In order to avoid patterns like `/build` in packages from accidentally + * impacting files outside the package we use `process.chdir()` to change + * the cwd to the package and execute `del()` without the `force` option + * so it will check that each file being deleted is within the package. + * + * `del()` does support a `cwd` option, but it's only for resolving the + * patterns and does not impact the cwd check. + */ + + const originalCwd = process.cwd(); + + try { + for (const _ref of toDelete) { + const { + pattern, + cwd + } = _ref; + process.chdir(cwd); + const promise = del__WEBPACK_IMPORTED_MODULE_1___default()(pattern); + ora__WEBPACK_IMPORTED_MODULE_2___default.a.promise(promise, Object(path__WEBPACK_IMPORTED_MODULE_3__["relative"])(originalCwd, Object(path__WEBPACK_IMPORTED_MODULE_3__["join"])(cwd, String(pattern)))); + await promise; + } + } finally { + process.chdir(originalCwd); + } + } + } + +}; + +/***/ }), +/* 174 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const {promisify} = __webpack_require__(29); +const path = __webpack_require__(16); +const globby = __webpack_require__(175); +const isGlob = __webpack_require__(187); +const slash = __webpack_require__(248); +const gracefulFs = __webpack_require__(250); +const isPathCwd = __webpack_require__(254); +const isPathInside = __webpack_require__(255); +const rimraf = __webpack_require__(256); +const pMap = __webpack_require__(257); + +const rimrafP = promisify(rimraf); + +const rimrafOptions = { + glob: false, + unlink: gracefulFs.unlink, + unlinkSync: gracefulFs.unlinkSync, + chmod: gracefulFs.chmod, + chmodSync: gracefulFs.chmodSync, + stat: gracefulFs.stat, + statSync: gracefulFs.statSync, + lstat: gracefulFs.lstat, + lstatSync: gracefulFs.lstatSync, + rmdir: gracefulFs.rmdir, + rmdirSync: gracefulFs.rmdirSync, + readdir: gracefulFs.readdir, + readdirSync: gracefulFs.readdirSync +}; + +function safeCheck(file, cwd) { + if (isPathCwd(file)) { + throw new Error('Cannot delete the current working directory. Can be overridden with the `force` option.'); + } + + if (!isPathInside(file, cwd)) { + throw new Error('Cannot delete files/directories outside the current working directory. Can be overridden with the `force` option.'); + } +} + +function normalizePatterns(patterns) { + patterns = Array.isArray(patterns) ? patterns : [patterns]; + + patterns = patterns.map(pattern => { + if (process.platform === 'win32' && isGlob(pattern) === false) { + return slash(pattern); + } + + return pattern; + }); + + return patterns; +} + +module.exports = async (patterns, {force, dryRun, cwd = process.cwd(), ...options} = {}) => { + options = { + expandDirectories: false, + onlyFiles: false, + followSymbolicLinks: false, + cwd, + ...options + }; + + patterns = normalizePatterns(patterns); + + const files = (await globby(patterns, options)) + .sort((a, b) => b.localeCompare(a)); + + const mapper = async file => { + file = path.resolve(cwd, file); + + if (!force) { + safeCheck(file, cwd); + } + + if (!dryRun) { + await rimrafP(file, rimrafOptions); + } + + return file; + }; + + const removedFiles = await pMap(files, mapper, options); + + removedFiles.sort((a, b) => a.localeCompare(b)); + + return removedFiles; +}; + +module.exports.sync = (patterns, {force, dryRun, cwd = process.cwd(), ...options} = {}) => { + options = { + expandDirectories: false, + onlyFiles: false, + followSymbolicLinks: false, + cwd, + ...options + }; + + patterns = normalizePatterns(patterns); + + const files = globby.sync(patterns, options) + .sort((a, b) => b.localeCompare(a)); + + const removedFiles = files.map(file => { + file = path.resolve(cwd, file); + + if (!force) { + safeCheck(file, cwd); + } + + if (!dryRun) { + rimraf.sync(file, rimrafOptions); + } + + return file; + }); + + removedFiles.sort((a, b) => a.localeCompare(b)); + + return removedFiles; +}; + + +/***/ }), +/* 175 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const fs = __webpack_require__(23); +const arrayUnion = __webpack_require__(176); +const merge2 = __webpack_require__(177); +const glob = __webpack_require__(37); +const fastGlob = __webpack_require__(178); +const dirGlob = __webpack_require__(244); +const gitignore = __webpack_require__(246); +const {FilterStream, UniqueStream} = __webpack_require__(249); + +const DEFAULT_FILTER = () => false; + +const isNegative = pattern => pattern[0] === '!'; + +const assertPatternsInput = patterns => { + if (!patterns.every(pattern => typeof pattern === 'string')) { + throw new TypeError('Patterns must be a string or an array of strings'); + } +}; + +const checkCwdOption = (options = {}) => { + if (!options.cwd) { + return; + } + + let stat; + try { + stat = fs.statSync(options.cwd); + } catch (_) { + return; + } + + if (!stat.isDirectory()) { + throw new Error('The `cwd` option must be a path to a directory'); + } +}; + +const getPathString = p => p.stats instanceof fs.Stats ? p.path : p; + +const generateGlobTasks = (patterns, taskOptions) => { + patterns = arrayUnion([].concat(patterns)); + assertPatternsInput(patterns); + checkCwdOption(taskOptions); + + const globTasks = []; + + taskOptions = { + ignore: [], + expandDirectories: true, + ...taskOptions + }; + + for (const [index, pattern] of patterns.entries()) { + if (isNegative(pattern)) { + continue; + } + + const ignore = patterns + .slice(index) + .filter(isNegative) + .map(pattern => pattern.slice(1)); + + const options = { + ...taskOptions, + ignore: taskOptions.ignore.concat(ignore) + }; + + globTasks.push({pattern, options}); + } + + return globTasks; +}; + +const globDirs = (task, fn) => { + let options = {}; + if (task.options.cwd) { + options.cwd = task.options.cwd; + } + + if (Array.isArray(task.options.expandDirectories)) { + options = { + ...options, + files: task.options.expandDirectories + }; + } else if (typeof task.options.expandDirectories === 'object') { + options = { + ...options, + ...task.options.expandDirectories + }; + } + + return fn(task.pattern, options); +}; + +const getPattern = (task, fn) => task.options.expandDirectories ? globDirs(task, fn) : [task.pattern]; + +const getFilterSync = options => { + return options && options.gitignore ? + gitignore.sync({cwd: options.cwd, ignore: options.ignore}) : + DEFAULT_FILTER; +}; + +const globToTask = task => glob => { + const {options} = task; + if (options.ignore && Array.isArray(options.ignore) && options.expandDirectories) { + options.ignore = dirGlob.sync(options.ignore); + } + + return { + pattern: glob, + options + }; +}; + +module.exports = async (patterns, options) => { + const globTasks = generateGlobTasks(patterns, options); + + const getFilter = async () => { + return options && options.gitignore ? + gitignore({cwd: options.cwd, ignore: options.ignore}) : + DEFAULT_FILTER; + }; + + const getTasks = async () => { + const tasks = await Promise.all(globTasks.map(async task => { + const globs = await getPattern(task, dirGlob); + return Promise.all(globs.map(globToTask(task))); + })); + + return arrayUnion(...tasks); + }; + + const [filter, tasks] = await Promise.all([getFilter(), getTasks()]); + const paths = await Promise.all(tasks.map(task => fastGlob(task.pattern, task.options))); + + return arrayUnion(...paths).filter(path_ => !filter(getPathString(path_))); +}; + +module.exports.sync = (patterns, options) => { + const globTasks = generateGlobTasks(patterns, options); + + const tasks = globTasks.reduce((tasks, task) => { + const newTask = getPattern(task, dirGlob.sync).map(globToTask(task)); + return tasks.concat(newTask); + }, []); + + const filter = getFilterSync(options); + + return tasks.reduce( + (matches, task) => arrayUnion(matches, fastGlob.sync(task.pattern, task.options)), + [] + ).filter(path_ => !filter(path_)); +}; + +module.exports.stream = (patterns, options) => { + const globTasks = generateGlobTasks(patterns, options); + + const tasks = globTasks.reduce((tasks, task) => { + const newTask = getPattern(task, dirGlob.sync).map(globToTask(task)); + return tasks.concat(newTask); + }, []); + + const filter = getFilterSync(options); + const filterStream = new FilterStream(p => !filter(p)); + const uniqueStream = new UniqueStream(); + + return merge2(tasks.map(task => fastGlob.stream(task.pattern, task.options))) + .pipe(filterStream) + .pipe(uniqueStream); +}; + +module.exports.generateGlobTasks = generateGlobTasks; + +module.exports.hasMagic = (patterns, options) => [] + .concat(patterns) + .some(pattern => glob.hasMagic(pattern, options)); + +module.exports.gitignore = gitignore; + + +/***/ }), +/* 176 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +module.exports = (...arguments_) => { + return [...new Set([].concat(...arguments_))]; +}; + + +/***/ }), +/* 177 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +/* + * merge2 + * https://github.com/teambition/merge2 + * + * Copyright (c) 2014-2016 Teambition + * Licensed under the MIT license. + */ +const Stream = __webpack_require__(28) +const PassThrough = Stream.PassThrough +const slice = Array.prototype.slice + +module.exports = merge2 + +function merge2 () { + const streamsQueue = [] + let merging = false + const args = slice.call(arguments) + let options = args[args.length - 1] + + if (options && !Array.isArray(options) && options.pipe == null) args.pop() + else options = {} + + const doEnd = options.end !== false + if (options.objectMode == null) options.objectMode = true + if (options.highWaterMark == null) options.highWaterMark = 64 * 1024 + const mergedStream = PassThrough(options) + + function addStream () { + for (let i = 0, len = arguments.length; i < len; i++) { + streamsQueue.push(pauseStreams(arguments[i], options)) + } + mergeStream() + return this + } + + function mergeStream () { + if (merging) return + merging = true + + let streams = streamsQueue.shift() + if (!streams) { + process.nextTick(endStream) + return + } + if (!Array.isArray(streams)) streams = [streams] + + let pipesCount = streams.length + 1 + + function next () { + if (--pipesCount > 0) return + merging = false + mergeStream() + } + + function pipe (stream) { + function onend () { + stream.removeListener('merge2UnpipeEnd', onend) + stream.removeListener('end', onend) + next() + } + // skip ended stream + if (stream._readableState.endEmitted) return next() + + stream.on('merge2UnpipeEnd', onend) + stream.on('end', onend) + stream.pipe(mergedStream, { end: false }) + // compatible for old stream + stream.resume() + } + + for (let i = 0; i < streams.length; i++) pipe(streams[i]) + + next() + } + + function endStream () { + merging = false + // emit 'queueDrain' when all streams merged. + mergedStream.emit('queueDrain') + return doEnd && mergedStream.end() + } + + mergedStream.setMaxListeners(0) + mergedStream.add = addStream + mergedStream.on('unpipe', function (stream) { + stream.emit('merge2UnpipeEnd') + }) + + if (args.length) addStream.apply(null, args) + return mergedStream +} + +// check and pause streams for pipe. +function pauseStreams (streams, options) { + if (!Array.isArray(streams)) { + // Backwards-compat with old-style streams + if (!streams._readableState && streams.pipe) streams = streams.pipe(PassThrough(options)) + if (!streams._readableState || !streams.pause || !streams.pipe) { + throw new Error('Only readable stream can be merged.') + } + streams.pause() + } else { + for (let i = 0, len = streams.length; i < len; i++) streams[i] = pauseStreams(streams[i], options) + } + return streams +} + + +/***/ }), +/* 178 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const taskManager = __webpack_require__(179); +const async_1 = __webpack_require__(207); +const stream_1 = __webpack_require__(240); +const sync_1 = __webpack_require__(241); +const settings_1 = __webpack_require__(243); +const utils = __webpack_require__(180); +function FastGlob(source, options) { + try { + assertPatternsInput(source); + } + catch (error) { + return Promise.reject(error); + } + const works = getWorks(source, async_1.default, options); + return Promise.all(works).then(utils.array.flatten); +} +(function (FastGlob) { + function sync(source, options) { + assertPatternsInput(source); + const works = getWorks(source, sync_1.default, options); + return utils.array.flatten(works); + } + FastGlob.sync = sync; + function stream(source, options) { + assertPatternsInput(source); + const works = getWorks(source, stream_1.default, options); + /** + * The stream returned by the provider cannot work with an asynchronous iterator. + * To support asynchronous iterators, regardless of the number of tasks, we always multiplex streams. + * This affects performance (+25%). I don't see best solution right now. + */ + return utils.stream.merge(works); + } + FastGlob.stream = stream; + function generateTasks(source, options) { + assertPatternsInput(source); + const patterns = [].concat(source); + const settings = new settings_1.default(options); + return taskManager.generate(patterns, settings); + } + FastGlob.generateTasks = generateTasks; +})(FastGlob || (FastGlob = {})); +function getWorks(source, _Provider, options) { + const patterns = [].concat(source); + const settings = new settings_1.default(options); + const tasks = taskManager.generate(patterns, settings); + const provider = new _Provider(settings); + return tasks.map(provider.read, provider); +} +function assertPatternsInput(source) { + if ([].concat(source).every(isString)) { + return; + } + throw new TypeError('Patterns must be a string or an array of strings'); +} +function isString(source) { + /* tslint:disable-next-line strict-type-predicates */ + return typeof source === 'string'; +} +module.exports = FastGlob; + + +/***/ }), +/* 179 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const utils = __webpack_require__(180); +function generate(patterns, settings) { + const positivePatterns = getPositivePatterns(patterns); + const negativePatterns = getNegativePatternsAsPositive(patterns, settings.ignore); + /** + * When the `caseSensitiveMatch` option is disabled, all patterns must be marked as dynamic, because we cannot check + * filepath directly (without read directory). + */ + const staticPatterns = !settings.caseSensitiveMatch ? [] : positivePatterns.filter(utils.pattern.isStaticPattern); + const dynamicPatterns = !settings.caseSensitiveMatch ? positivePatterns : positivePatterns.filter(utils.pattern.isDynamicPattern); + const staticTasks = convertPatternsToTasks(staticPatterns, negativePatterns, /* dynamic */ false); + const dynamicTasks = convertPatternsToTasks(dynamicPatterns, negativePatterns, /* dynamic */ true); + return staticTasks.concat(dynamicTasks); +} +exports.generate = generate; +function convertPatternsToTasks(positive, negative, dynamic) { + const positivePatternsGroup = groupPatternsByBaseDirectory(positive); + // When we have a global group – there is no reason to divide the patterns into independent tasks. + // In this case, the global task covers the rest. + if ('.' in positivePatternsGroup) { + const task = convertPatternGroupToTask('.', positive, negative, dynamic); + return [task]; + } + return convertPatternGroupsToTasks(positivePatternsGroup, negative, dynamic); +} +exports.convertPatternsToTasks = convertPatternsToTasks; +function getPositivePatterns(patterns) { + return utils.pattern.getPositivePatterns(patterns); +} +exports.getPositivePatterns = getPositivePatterns; +function getNegativePatternsAsPositive(patterns, ignore) { + const negative = utils.pattern.getNegativePatterns(patterns).concat(ignore); + const positive = negative.map(utils.pattern.convertToPositivePattern); + return positive; +} +exports.getNegativePatternsAsPositive = getNegativePatternsAsPositive; +function groupPatternsByBaseDirectory(patterns) { + return patterns.reduce((collection, pattern) => { + const base = utils.pattern.getBaseDirectory(pattern); + if (base in collection) { + collection[base].push(pattern); + } + else { + collection[base] = [pattern]; + } + return collection; + }, {}); +} +exports.groupPatternsByBaseDirectory = groupPatternsByBaseDirectory; +function convertPatternGroupsToTasks(positive, negative, dynamic) { + return Object.keys(positive).map((base) => { + return convertPatternGroupToTask(base, positive[base], negative, dynamic); + }); +} +exports.convertPatternGroupsToTasks = convertPatternGroupsToTasks; +function convertPatternGroupToTask(base, positive, negative, dynamic) { + return { + dynamic, + positive, + negative, + base, + patterns: [].concat(positive, negative.map(utils.pattern.convertToNegativePattern)) + }; +} +exports.convertPatternGroupToTask = convertPatternGroupToTask; + + +/***/ }), +/* 180 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const array = __webpack_require__(181); +exports.array = array; +const errno = __webpack_require__(182); +exports.errno = errno; +const fs = __webpack_require__(183); +exports.fs = fs; +const path = __webpack_require__(184); +exports.path = path; +const pattern = __webpack_require__(185); +exports.pattern = pattern; +const stream = __webpack_require__(206); +exports.stream = stream; + + +/***/ }), +/* 181 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +function flatten(items) { + return items.reduce((collection, item) => [].concat(collection, item), []); +} +exports.flatten = flatten; + + +/***/ }), +/* 182 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +function isEnoentCodeError(error) { + return error.code === 'ENOENT'; +} +exports.isEnoentCodeError = isEnoentCodeError; + + +/***/ }), +/* 183 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +class DirentFromStats { + constructor(name, stats) { + this.name = name; + this.isBlockDevice = stats.isBlockDevice.bind(stats); + this.isCharacterDevice = stats.isCharacterDevice.bind(stats); + this.isDirectory = stats.isDirectory.bind(stats); + this.isFIFO = stats.isFIFO.bind(stats); + this.isFile = stats.isFile.bind(stats); + this.isSocket = stats.isSocket.bind(stats); + this.isSymbolicLink = stats.isSymbolicLink.bind(stats); + } +} +function createDirentFromStats(name, stats) { + return new DirentFromStats(name, stats); +} +exports.createDirentFromStats = createDirentFromStats; + + +/***/ }), +/* 184 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const path = __webpack_require__(16); +/** + * Designed to work only with simple paths: `dir\\file`. + */ +function unixify(filepath) { + return filepath.replace(/\\/g, '/'); +} +exports.unixify = unixify; +function makeAbsolute(cwd, filepath) { + return path.resolve(cwd, filepath); +} +exports.makeAbsolute = makeAbsolute; + + +/***/ }), +/* 185 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const path = __webpack_require__(16); +const globParent = __webpack_require__(186); +const isGlob = __webpack_require__(187); +const micromatch = __webpack_require__(189); +const GLOBSTAR = '**'; +function isStaticPattern(pattern) { + return !isDynamicPattern(pattern); +} +exports.isStaticPattern = isStaticPattern; +function isDynamicPattern(pattern) { + return isGlob(pattern, { strict: false }); +} +exports.isDynamicPattern = isDynamicPattern; +function convertToPositivePattern(pattern) { + return isNegativePattern(pattern) ? pattern.slice(1) : pattern; +} +exports.convertToPositivePattern = convertToPositivePattern; +function convertToNegativePattern(pattern) { + return '!' + pattern; +} +exports.convertToNegativePattern = convertToNegativePattern; +function isNegativePattern(pattern) { + return pattern.startsWith('!') && pattern[1] !== '('; +} +exports.isNegativePattern = isNegativePattern; +function isPositivePattern(pattern) { + return !isNegativePattern(pattern); +} +exports.isPositivePattern = isPositivePattern; +function getNegativePatterns(patterns) { + return patterns.filter(isNegativePattern); +} +exports.getNegativePatterns = getNegativePatterns; +function getPositivePatterns(patterns) { + return patterns.filter(isPositivePattern); +} +exports.getPositivePatterns = getPositivePatterns; +function getBaseDirectory(pattern) { + return globParent(pattern); +} +exports.getBaseDirectory = getBaseDirectory; +function hasGlobStar(pattern) { + return pattern.indexOf(GLOBSTAR) !== -1; +} +exports.hasGlobStar = hasGlobStar; +function endsWithSlashGlobStar(pattern) { + return pattern.endsWith('/' + GLOBSTAR); +} +exports.endsWithSlashGlobStar = endsWithSlashGlobStar; +function isAffectDepthOfReadingPattern(pattern) { + const basename = path.basename(pattern); + return endsWithSlashGlobStar(pattern) || isStaticPattern(basename); +} +exports.isAffectDepthOfReadingPattern = isAffectDepthOfReadingPattern; +function getNaiveDepth(pattern) { + const base = getBaseDirectory(pattern); + const patternDepth = pattern.split('/').length; + const patternBaseDepth = base.split('/').length; + /** + * This is a hack for pattern that has no base directory. + * + * This is related to the `*\something\*` pattern. + */ + if (base === '.') { + return patternDepth - patternBaseDepth; + } + return patternDepth - patternBaseDepth - 1; +} +exports.getNaiveDepth = getNaiveDepth; +function getMaxNaivePatternsDepth(patterns) { + return patterns.reduce((max, pattern) => { + const depth = getNaiveDepth(pattern); + return depth > max ? depth : max; + }, 0); +} +exports.getMaxNaivePatternsDepth = getMaxNaivePatternsDepth; +function makeRe(pattern, options) { + return micromatch.makeRe(pattern, options); +} +exports.makeRe = makeRe; +function convertPatternsToRe(patterns, options) { + return patterns.map((pattern) => makeRe(pattern, options)); +} +exports.convertPatternsToRe = convertPatternsToRe; +function matchAny(entry, patternsRe) { + const filepath = entry.replace(/^\.[\\\/]/, ''); + return patternsRe.some((patternRe) => patternRe.test(filepath)); +} +exports.matchAny = matchAny; + + +/***/ }), +/* 186 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var isGlob = __webpack_require__(187); +var pathPosixDirname = __webpack_require__(16).posix.dirname; +var isWin32 = __webpack_require__(11).platform() === 'win32'; + +var slash = '/'; +var backslash = /\\/g; +var enclosure = /[\{\[].*[\/]*.*[\}\]]$/; +var globby = /(^|[^\\])([\{\[]|\([^\)]+$)/; +var escaped = /\\([\*\?\|\[\]\(\)\{\}])/g; + +module.exports = function globParent(str) { + // flip windows path separators + if (isWin32 && str.indexOf(slash) < 0) { + str = str.replace(backslash, slash); + } + + // special case for strings ending in enclosure containing path separator + if (enclosure.test(str)) { + str += slash; + } + + // preserves full path in case of trailing path separator + str += 'a'; + + // remove path parts that are globby + do { + str = pathPosixDirname(str); + } while (isGlob(str) || globby.test(str)); + + // remove escape chars and return result + return str.replace(escaped, '$1'); +}; + + +/***/ }), +/* 187 */ +/***/ (function(module, exports, __webpack_require__) { + +/*! + * is-glob + * + * Copyright (c) 2014-2017, Jon Schlinkert. + * Released under the MIT License. + */ + +var isExtglob = __webpack_require__(188); +var chars = { '{': '}', '(': ')', '[': ']'}; +var strictRegex = /\\(.)|(^!|\*|[\].+)]\?|\[[^\\\]]+\]|\{[^\\}]+\}|\(\?[:!=][^\\)]+\)|\([^|]+\|[^\\)]+\))/; +var relaxedRegex = /\\(.)|(^!|[*?{}()[\]]|\(\?)/; + +module.exports = function isGlob(str, options) { + if (typeof str !== 'string' || str === '') { + return false; + } + + if (isExtglob(str)) { + return true; + } + + var regex = strictRegex; + var match; + + // optionally relax regex + if (options && options.strict === false) { + regex = relaxedRegex; + } + + while ((match = regex.exec(str))) { + if (match[2]) return true; + var idx = match.index + match[0].length; + + // if an open bracket/brace/paren is escaped, + // set the index to the next closing character + var open = match[1]; + var close = open ? chars[open] : null; + if (open && close) { + var n = str.indexOf(close, idx); + if (n !== -1) { + idx = n + 1; + } + } + + str = str.slice(idx); + } + return false; +}; + + +/***/ }), +/* 188 */ +/***/ (function(module, exports) { + +/*! + * is-extglob + * + * Copyright (c) 2014-2016, Jon Schlinkert. + * Licensed under the MIT License. + */ + +module.exports = function isExtglob(str) { + if (typeof str !== 'string' || str === '') { + return false; + } + + var match; + while ((match = /(\\).|([@?!+*]\(.*\))/g.exec(str))) { + if (match[2]) return true; + str = str.slice(match.index + match[0].length); + } + + return false; +}; + + +/***/ }), +/* 189 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const util = __webpack_require__(29); +const braces = __webpack_require__(190); +const picomatch = __webpack_require__(200); +const utils = __webpack_require__(203); +const isEmptyString = val => typeof val === 'string' && (val === '' || val === './'); + +/** + * Returns an array of strings that match one or more glob patterns. + * + * ```js + * const mm = require('micromatch'); + * // mm(list, patterns[, options]); + * + * console.log(mm(['a.js', 'a.txt'], ['*.js'])); + * //=> [ 'a.js' ] + * ``` + * @param {String|Array} list List of strings to match. + * @param {String|Array} patterns One or more glob patterns to use for matching. + * @param {Object} options See available [options](#options) + * @return {Array} Returns an array of matches + * @summary false + * @api public + */ + +const micromatch = (list, patterns, options) => { + patterns = [].concat(patterns); + list = [].concat(list); + + let omit = new Set(); + let keep = new Set(); + let items = new Set(); + let negatives = 0; + + let onResult = state => { + items.add(state.output); + if (options && options.onResult) { + options.onResult(state); + } + }; + + for (let i = 0; i < patterns.length; i++) { + let isMatch = picomatch(String(patterns[i]), { ...options, onResult }, true); + let negated = isMatch.state.negated || isMatch.state.negatedExtglob; + if (negated) negatives++; + + for (let item of list) { + let matched = isMatch(item, true); + + let match = negated ? !matched.isMatch : matched.isMatch; + if (!match) continue; + + if (negated) { + omit.add(matched.output); + } else { + omit.delete(matched.output); + keep.add(matched.output); + } + } + } + + let result = negatives === patterns.length ? [...items] : [...keep]; + let matches = result.filter(item => !omit.has(item)); + + if (options && matches.length === 0) { + if (options.failglob === true) { + throw new Error(`No matches found for "${patterns.join(', ')}"`); + } + + if (options.nonull === true || options.nullglob === true) { + return options.unescape ? patterns.map(p => p.replace(/\\/g, '')) : patterns; + } + } + + return matches; +}; + +/** + * Backwards compatibility + */ + +micromatch.match = micromatch; + +/** + * Returns a matcher function from the given glob `pattern` and `options`. + * The returned function takes a string to match as its only argument and returns + * true if the string is a match. + * + * ```js + * const mm = require('micromatch'); + * // mm.matcher(pattern[, options]); + * + * const isMatch = mm.matcher('*.!(*a)'); + * console.log(isMatch('a.a')); //=> false + * console.log(isMatch('a.b')); //=> true + * ``` + * @param {String} `pattern` Glob pattern + * @param {Object} `options` + * @return {Function} Returns a matcher function. + * @api public + */ + +micromatch.matcher = (pattern, options) => picomatch(pattern, options); + +/** + * Returns true if **any** of the given glob `patterns` match the specified `string`. + * + * ```js + * const mm = require('micromatch'); + * // mm.isMatch(string, patterns[, options]); + * + * console.log(mm.isMatch('a.a', ['b.*', '*.a'])); //=> true + * console.log(mm.isMatch('a.a', 'b.*')); //=> false + * ``` + * @param {String} str The string to test. + * @param {String|Array} patterns One or more glob patterns to use for matching. + * @param {Object} [options] See available [options](#options). + * @return {Boolean} Returns true if any patterns match `str` + * @api public + */ + +micromatch.isMatch = (str, patterns, options) => picomatch(patterns, options)(str); + +/** + * Backwards compatibility + */ + +micromatch.any = micromatch.isMatch; + +/** + * Returns a list of strings that _**do not match any**_ of the given `patterns`. + * + * ```js + * const mm = require('micromatch'); + * // mm.not(list, patterns[, options]); + * + * console.log(mm.not(['a.a', 'b.b', 'c.c'], '*.a')); + * //=> ['b.b', 'c.c'] + * ``` + * @param {Array} `list` Array of strings to match. + * @param {String|Array} `patterns` One or more glob pattern to use for matching. + * @param {Object} `options` See available [options](#options) for changing how matches are performed + * @return {Array} Returns an array of strings that **do not match** the given patterns. + * @api public + */ + +micromatch.not = (list, patterns, options = {}) => { + patterns = [].concat(patterns).map(String); + let result = new Set(); + let items = []; + + let onResult = state => { + if (options.onResult) options.onResult(state); + items.push(state.output); + }; + + let matches = micromatch(list, patterns, { ...options, onResult }); + + for (let item of items) { + if (!matches.includes(item)) { + result.add(item); + } + } + return [...result]; +}; + +/** + * Returns true if the given `string` contains the given pattern. Similar + * to [.isMatch](#isMatch) but the pattern can match any part of the string. + * + * ```js + * var mm = require('micromatch'); + * // mm.contains(string, pattern[, options]); + * + * console.log(mm.contains('aa/bb/cc', '*b')); + * //=> true + * console.log(mm.contains('aa/bb/cc', '*d')); + * //=> false + * ``` + * @param {String} `str` The string to match. + * @param {String|Array} `patterns` Glob pattern to use for matching. + * @param {Object} `options` See available [options](#options) for changing how matches are performed + * @return {Boolean} Returns true if the patter matches any part of `str`. + * @api public + */ + +micromatch.contains = (str, pattern, options) => { + if (typeof str !== 'string') { + throw new TypeError(`Expected a string: "${util.inspect(str)}"`); + } + + if (Array.isArray(pattern)) { + return pattern.some(p => micromatch.contains(str, p, options)); + } + + if (typeof pattern === 'string') { + if (isEmptyString(str) || isEmptyString(pattern)) { + return false; + } + + if (str.includes(pattern) || (str.startsWith('./') && str.slice(2).includes(pattern))) { + return true; + } + } + + return micromatch.isMatch(str, pattern, { ...options, contains: true }); +}; + +/** + * Filter the keys of the given object with the given `glob` pattern + * and `options`. Does not attempt to match nested keys. If you need this feature, + * use [glob-object][] instead. + * + * ```js + * const mm = require('micromatch'); + * // mm.matchKeys(object, patterns[, options]); + * + * const obj = { aa: 'a', ab: 'b', ac: 'c' }; + * console.log(mm.matchKeys(obj, '*b')); + * //=> { ab: 'b' } + * ``` + * @param {Object} `object` The object with keys to filter. + * @param {String|Array} `patterns` One or more glob patterns to use for matching. + * @param {Object} `options` See available [options](#options) for changing how matches are performed + * @return {Object} Returns an object with only keys that match the given patterns. + * @api public + */ + +micromatch.matchKeys = (obj, patterns, options) => { + if (!utils.isObject(obj)) { + throw new TypeError('Expected the first argument to be an object'); + } + let keys = micromatch(Object.keys(obj), patterns, options); + let res = {}; + for (let key of keys) res[key] = obj[key]; + return res; +}; + +/** + * Returns true if some of the strings in the given `list` match any of the given glob `patterns`. + * + * ```js + * const mm = require('micromatch'); + * // mm.some(list, patterns[, options]); + * + * console.log(mm.some(['foo.js', 'bar.js'], ['*.js', '!foo.js'])); + * // true + * console.log(mm.some(['foo.js'], ['*.js', '!foo.js'])); + * // false + * ``` + * @param {String|Array} `list` The string or array of strings to test. Returns as soon as the first match is found. + * @param {String|Array} `patterns` One or more glob patterns to use for matching. + * @param {Object} `options` See available [options](#options) for changing how matches are performed + * @return {Boolean} Returns true if any patterns match `str` + * @api public + */ + +micromatch.some = (list, patterns, options) => { + let items = [].concat(list); + + for (let pattern of [].concat(patterns)) { + let isMatch = picomatch(String(pattern), options); + if (items.some(item => isMatch(item))) { + return true; + } + } + return false; +}; + +/** + * Returns true if every string in the given `list` matches + * any of the given glob `patterns`. + * + * ```js + * const mm = require('micromatch'); + * // mm.every(list, patterns[, options]); + * + * console.log(mm.every('foo.js', ['foo.js'])); + * // true + * console.log(mm.every(['foo.js', 'bar.js'], ['*.js'])); + * // true + * console.log(mm.every(['foo.js', 'bar.js'], ['*.js', '!foo.js'])); + * // false + * console.log(mm.every(['foo.js'], ['*.js', '!foo.js'])); + * // false + * ``` + * @param {String|Array} `list` The string or array of strings to test. + * @param {String|Array} `patterns` One or more glob patterns to use for matching. + * @param {Object} `options` See available [options](#options) for changing how matches are performed + * @return {Boolean} Returns true if any patterns match `str` + * @api public + */ + +micromatch.every = (list, patterns, options) => { + let items = [].concat(list); + + for (let pattern of [].concat(patterns)) { + let isMatch = picomatch(String(pattern), options); + if (!items.every(item => isMatch(item))) { + return false; + } + } + return true; +}; + +/** + * Returns true if **all** of the given `patterns` match + * the specified string. + * + * ```js + * const mm = require('micromatch'); + * // mm.all(string, patterns[, options]); + * + * console.log(mm.all('foo.js', ['foo.js'])); + * // true + * + * console.log(mm.all('foo.js', ['*.js', '!foo.js'])); + * // false + * + * console.log(mm.all('foo.js', ['*.js', 'foo.js'])); + * // true + * + * console.log(mm.all('foo.js', ['*.js', 'f*', '*o*', '*o.js'])); + * // true + * ``` + * @param {String|Array} `str` The string to test. + * @param {String|Array} `patterns` One or more glob patterns to use for matching. + * @param {Object} `options` See available [options](#options) for changing how matches are performed + * @return {Boolean} Returns true if any patterns match `str` + * @api public + */ + +micromatch.all = (str, patterns, options) => { + if (typeof str !== 'string') { + throw new TypeError(`Expected a string: "${util.inspect(str)}"`); + } + + return [].concat(patterns).every(p => picomatch(p, options)(str)); +}; + +/** + * Returns an array of matches captured by `pattern` in `string, or `null` if the pattern did not match. + * + * ```js + * const mm = require('micromatch'); + * // mm.capture(pattern, string[, options]); + * + * console.log(mm.capture('test/*.js', 'test/foo.js')); + * //=> ['foo'] + * console.log(mm.capture('test/*.js', 'foo/bar.css')); + * //=> null + * ``` + * @param {String} `glob` Glob pattern to use for matching. + * @param {String} `input` String to match + * @param {Object} `options` See available [options](#options) for changing how matches are performed + * @return {Boolean} Returns an array of captures if the input matches the glob pattern, otherwise `null`. + * @api public + */ + +micromatch.capture = (glob, input, options) => { + let posix = utils.isWindows(options); + let regex = picomatch.makeRe(String(glob), { ...options, capture: true }); + let match = regex.exec(posix ? utils.toPosixSlashes(input) : input); + + if (match) { + return match.slice(1).map(v => v === void 0 ? '' : v); + } +}; + +/** + * Create a regular expression from the given glob `pattern`. + * + * ```js + * const mm = require('micromatch'); + * // mm.makeRe(pattern[, options]); + * + * console.log(mm.makeRe('*.js')); + * //=> /^(?:(\.[\\\/])?(?!\.)(?=.)[^\/]*?\.js)$/ + * ``` + * @param {String} `pattern` A glob pattern to convert to regex. + * @param {Object} `options` + * @return {RegExp} Returns a regex created from the given pattern. + * @api public + */ + +micromatch.makeRe = (...args) => picomatch.makeRe(...args); + +/** + * Scan a glob pattern to separate the pattern into segments. Used + * by the [split](#split) method. + * + * ```js + * const mm = require('micromatch'); + * const state = mm.scan(pattern[, options]); + * ``` + * @param {String} `pattern` + * @param {Object} `options` + * @return {Object} Returns an object with + * @api public + */ + +micromatch.scan = (...args) => picomatch.scan(...args); + +/** + * Parse a glob pattern to create the source string for a regular + * expression. + * + * ```js + * const mm = require('micromatch'); + * const state = mm(pattern[, options]); + * ``` + * @param {String} `glob` + * @param {Object} `options` + * @return {Object} Returns an object with useful properties and output to be used as regex source string. + * @api public + */ + +micromatch.parse = (patterns, options) => { + let res = []; + for (let pattern of [].concat(patterns || [])) { + for (let str of braces(String(pattern), options)) { + res.push(picomatch.parse(str, options)); + } + } + return res; +}; + +/** + * Process the given brace `pattern`. + * + * ```js + * const { braces } = require('micromatch'); + * console.log(braces('foo/{a,b,c}/bar')); + * //=> [ 'foo/(a|b|c)/bar' ] + * + * console.log(braces('foo/{a,b,c}/bar', { expand: true })); + * //=> [ 'foo/a/bar', 'foo/b/bar', 'foo/c/bar' ] + * ``` + * @param {String} `pattern` String with brace pattern to process. + * @param {Object} `options` Any [options](#options) to change how expansion is performed. See the [braces][] library for all available options. + * @return {Array} + * @api public + */ + +micromatch.braces = (pattern, options) => { + if (typeof pattern !== 'string') throw new TypeError('Expected a string'); + if ((options && options.nobrace === true) || !/\{.*\}/.test(pattern)) { + return [pattern]; + } + return braces(pattern, options); +}; + +/** + * Expand braces + */ + +micromatch.braceExpand = (pattern, options) => { + if (typeof pattern !== 'string') throw new TypeError('Expected a string'); + return micromatch.braces(pattern, { ...options, expand: true }); +}; + +/** + * Expose micromatch + */ + +module.exports = micromatch; + + +/***/ }), +/* 190 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const stringify = __webpack_require__(191); +const compile = __webpack_require__(193); +const expand = __webpack_require__(197); +const parse = __webpack_require__(198); + +/** + * Expand the given pattern or create a regex-compatible string. + * + * ```js + * const braces = require('braces'); + * console.log(braces('{a,b,c}', { compile: true })); //=> ['(a|b|c)'] + * console.log(braces('{a,b,c}')); //=> ['a', 'b', 'c'] + * ``` + * @param {String} `str` + * @param {Object} `options` + * @return {String} + * @api public + */ + +const braces = (input, options = {}) => { + let output = []; + + if (Array.isArray(input)) { + for (let pattern of input) { + let result = braces.create(pattern, options); + if (Array.isArray(result)) { + output.push(...result); + } else { + output.push(result); + } + } + } else { + output = [].concat(braces.create(input, options)); + } + + if (options && options.expand === true && options.nodupes === true) { + output = [...new Set(output)]; + } + return output; +}; + +/** + * Parse the given `str` with the given `options`. + * + * ```js + * // braces.parse(pattern, [, options]); + * const ast = braces.parse('a/{b,c}/d'); + * console.log(ast); + * ``` + * @param {String} pattern Brace pattern to parse + * @param {Object} options + * @return {Object} Returns an AST + * @api public + */ + +braces.parse = (input, options = {}) => parse(input, options); + +/** + * Creates a braces string from an AST, or an AST node. + * + * ```js + * const braces = require('braces'); + * let ast = braces.parse('foo/{a,b}/bar'); + * console.log(stringify(ast.nodes[2])); //=> '{a,b}' + * ``` + * @param {String} `input` Brace pattern or AST. + * @param {Object} `options` + * @return {Array} Returns an array of expanded values. + * @api public + */ + +braces.stringify = (input, options = {}) => { + if (typeof input === 'string') { + return stringify(braces.parse(input, options), options); + } + return stringify(input, options); +}; + +/** + * Compiles a brace pattern into a regex-compatible, optimized string. + * This method is called by the main [braces](#braces) function by default. + * + * ```js + * const braces = require('braces'); + * console.log(braces.compile('a/{b,c}/d')); + * //=> ['a/(b|c)/d'] + * ``` + * @param {String} `input` Brace pattern or AST. + * @param {Object} `options` + * @return {Array} Returns an array of expanded values. + * @api public + */ + +braces.compile = (input, options = {}) => { + if (typeof input === 'string') { + input = braces.parse(input, options); + } + return compile(input, options); +}; + +/** + * Expands a brace pattern into an array. This method is called by the + * main [braces](#braces) function when `options.expand` is true. Before + * using this method it's recommended that you read the [performance notes](#performance)) + * and advantages of using [.compile](#compile) instead. + * + * ```js + * const braces = require('braces'); + * console.log(braces.expand('a/{b,c}/d')); + * //=> ['a/b/d', 'a/c/d']; + * ``` + * @param {String} `pattern` Brace pattern + * @param {Object} `options` + * @return {Array} Returns an array of expanded values. + * @api public + */ + +braces.expand = (input, options = {}) => { + if (typeof input === 'string') { + input = braces.parse(input, options); + } + + let result = expand(input, options); + + // filter out empty strings if specified + if (options.noempty === true) { + result = result.filter(Boolean); + } + + // filter out duplicates if specified + if (options.nodupes === true) { + result = [...new Set(result)]; + } + + return result; +}; + +/** + * Processes a brace pattern and returns either an expanded array + * (if `options.expand` is true), a highly optimized regex-compatible string. + * This method is called by the main [braces](#braces) function. + * + * ```js + * const braces = require('braces'); + * console.log(braces.create('user-{200..300}/project-{a,b,c}-{1..10}')) + * //=> 'user-(20[0-9]|2[1-9][0-9]|300)/project-(a|b|c)-([1-9]|10)' + * ``` + * @param {String} `pattern` Brace pattern + * @param {Object} `options` + * @return {Array} Returns an array of expanded values. + * @api public + */ + +braces.create = (input, options = {}) => { + if (input === '' || input.length < 3) { + return [input]; + } + + return options.expand !== true + ? braces.compile(input, options) + : braces.expand(input, options); +}; + +/** + * Expose "braces" + */ + +module.exports = braces; + + +/***/ }), +/* 191 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const utils = __webpack_require__(192); + +module.exports = (ast, options = {}) => { + let stringify = (node, parent = {}) => { + let invalidBlock = options.escapeInvalid && utils.isInvalidBrace(parent); + let invalidNode = node.invalid === true && options.escapeInvalid === true; + let output = ''; + + if (node.value) { + if ((invalidBlock || invalidNode) && utils.isOpenOrClose(node)) { + return '\\' + node.value; + } + return node.value; + } + + if (node.value) { + return node.value; + } + + if (node.nodes) { + for (let child of node.nodes) { + output += stringify(child); + } + } + return output; + }; + + return stringify(ast); +}; + + + +/***/ }), +/* 192 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +exports.isInteger = num => { + if (typeof num === 'number') { + return Number.isInteger(num); + } + if (typeof num === 'string' && num.trim() !== '') { + return Number.isInteger(Number(num)); + } + return false; +}; + +/** + * Find a node of the given type + */ + +exports.find = (node, type) => node.nodes.find(node => node.type === type); + +/** + * Find a node of the given type + */ + +exports.exceedsLimit = (min, max, step = 1, limit) => { + if (limit === false) return false; + if (!exports.isInteger(min) || !exports.isInteger(max)) return false; + return ((Number(max) - Number(min)) / Number(step)) >= limit; +}; + +/** + * Escape the given node with '\\' before node.value + */ + +exports.escapeNode = (block, n = 0, type) => { + let node = block.nodes[n]; + if (!node) return; + + if ((type && node.type === type) || node.type === 'open' || node.type === 'close') { + if (node.escaped !== true) { + node.value = '\\' + node.value; + node.escaped = true; + } + } +}; + +/** + * Returns true if the given brace node should be enclosed in literal braces + */ + +exports.encloseBrace = node => { + if (node.type !== 'brace') return false; + if ((node.commas >> 0 + node.ranges >> 0) === 0) { + node.invalid = true; + return true; + } + return false; +}; + +/** + * Returns true if a brace node is invalid. + */ + +exports.isInvalidBrace = block => { + if (block.type !== 'brace') return false; + if (block.invalid === true || block.dollar) return true; + if ((block.commas >> 0 + block.ranges >> 0) === 0) { + block.invalid = true; + return true; + } + if (block.open !== true || block.close !== true) { + block.invalid = true; + return true; + } + return false; +}; + +/** + * Returns true if a node is an open or close node + */ + +exports.isOpenOrClose = node => { + if (node.type === 'open' || node.type === 'close') { + return true; + } + return node.open === true || node.close === true; +}; + +/** + * Reduce an array of text nodes. + */ + +exports.reduce = nodes => nodes.reduce((acc, node) => { + if (node.type === 'text') acc.push(node.value); + if (node.type === 'range') node.type = 'text'; + return acc; +}, []); + +/** + * Flatten an array + */ + +exports.flatten = (...args) => { + const result = []; + const flat = arr => { + for (let i = 0; i < arr.length; i++) { + let ele = arr[i]; + Array.isArray(ele) ? flat(ele, result) : ele !== void 0 && result.push(ele); + } + return result; + }; + flat(args); + return result; +}; + + +/***/ }), +/* 193 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const fill = __webpack_require__(194); +const utils = __webpack_require__(192); + +const compile = (ast, options = {}) => { + let walk = (node, parent = {}) => { + let invalidBlock = utils.isInvalidBrace(parent); + let invalidNode = node.invalid === true && options.escapeInvalid === true; + let invalid = invalidBlock === true || invalidNode === true; + let prefix = options.escapeInvalid === true ? '\\' : ''; + let output = ''; + + if (node.isOpen === true) { + return prefix + node.value; + } + if (node.isClose === true) { + return prefix + node.value; + } + + if (node.type === 'open') { + return invalid ? (prefix + node.value) : '('; + } + + if (node.type === 'close') { + return invalid ? (prefix + node.value) : ')'; + } + + if (node.type === 'comma') { + return node.prev.type === 'comma' ? '' : (invalid ? node.value : '|'); + } + + if (node.value) { + return node.value; + } + + if (node.nodes && node.ranges > 0) { + let args = utils.reduce(node.nodes); + let range = fill(...args, { ...options, wrap: false, toRegex: true }); + + if (range.length !== 0) { + return args.length > 1 && range.length > 1 ? `(${range})` : range; + } + } + + if (node.nodes) { + for (let child of node.nodes) { + output += walk(child, node); + } + } + return output; + }; + + return walk(ast); +}; + +module.exports = compile; + + +/***/ }), +/* 194 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/*! + * fill-range + * + * Copyright (c) 2014-present, Jon Schlinkert. + * Licensed under the MIT License. + */ + + + +const util = __webpack_require__(29); +const toRegexRange = __webpack_require__(195); + +const isObject = val => val !== null && typeof val === 'object' && !Array.isArray(val); + +const transform = toNumber => { + return value => toNumber === true ? Number(value) : String(value); +}; + +const isValidValue = value => { + return typeof value === 'number' || (typeof value === 'string' && value !== ''); +}; + +const isNumber = num => Number.isInteger(+num); + +const zeros = input => { + let value = `${input}`; + let index = -1; + if (value[0] === '-') value = value.slice(1); + if (value === '0') return false; + while (value[++index] === '0'); + return index > 0; +}; + +const stringify = (start, end, options) => { + if (typeof start === 'string' || typeof end === 'string') { + return true; + } + return options.stringify === true; +}; + +const pad = (input, maxLength, toNumber) => { + if (maxLength > 0) { + let dash = input[0] === '-' ? '-' : ''; + if (dash) input = input.slice(1); + input = (dash + input.padStart(dash ? maxLength - 1 : maxLength, '0')); + } + if (toNumber === false) { + return String(input); + } + return input; +}; + +const toMaxLen = (input, maxLength) => { + let negative = input[0] === '-' ? '-' : ''; + if (negative) { + input = input.slice(1); + maxLength--; + } + while (input.length < maxLength) input = '0' + input; + return negative ? ('-' + input) : input; +}; + +const toSequence = (parts, options) => { + parts.negatives.sort((a, b) => a < b ? -1 : a > b ? 1 : 0); + parts.positives.sort((a, b) => a < b ? -1 : a > b ? 1 : 0); + + let prefix = options.capture ? '' : '?:'; + let positives = ''; + let negatives = ''; + let result; + + if (parts.positives.length) { + positives = parts.positives.join('|'); + } + + if (parts.negatives.length) { + negatives = `-(${prefix}${parts.negatives.join('|')})`; + } + + if (positives && negatives) { + result = `${positives}|${negatives}`; + } else { + result = positives || negatives; + } + + if (options.wrap) { + return `(${prefix}${result})`; + } + + return result; +}; + +const toRange = (a, b, isNumbers, options) => { + if (isNumbers) { + return toRegexRange(a, b, { wrap: false, ...options }); + } + + let start = String.fromCharCode(a); + if (a === b) return start; + + let stop = String.fromCharCode(b); + return `[${start}-${stop}]`; +}; + +const toRegex = (start, end, options) => { + if (Array.isArray(start)) { + let wrap = options.wrap === true; + let prefix = options.capture ? '' : '?:'; + return wrap ? `(${prefix}${start.join('|')})` : start.join('|'); + } + return toRegexRange(start, end, options); +}; + +const rangeError = (...args) => { + return new RangeError('Invalid range arguments: ' + util.inspect(...args)); +}; + +const invalidRange = (start, end, options) => { + if (options.strictRanges === true) throw rangeError([start, end]); + return []; +}; + +const invalidStep = (step, options) => { + if (options.strictRanges === true) { + throw new TypeError(`Expected step "${step}" to be a number`); + } + return []; +}; + +const fillNumbers = (start, end, step = 1, options = {}) => { + let a = Number(start); + let b = Number(end); + + if (!Number.isInteger(a) || !Number.isInteger(b)) { + if (options.strictRanges === true) throw rangeError([start, end]); + return []; + } + + // fix negative zero + if (a === 0) a = 0; + if (b === 0) b = 0; + + let descending = a > b; + let startString = String(start); + let endString = String(end); + let stepString = String(step); + step = Math.max(Math.abs(step), 1); + + let padded = zeros(startString) || zeros(endString) || zeros(stepString); + let maxLen = padded ? Math.max(startString.length, endString.length, stepString.length) : 0; + let toNumber = padded === false && stringify(start, end, options) === false; + let format = options.transform || transform(toNumber); + + if (options.toRegex && step === 1) { + return toRange(toMaxLen(start, maxLen), toMaxLen(end, maxLen), true, options); + } + + let parts = { negatives: [], positives: [] }; + let push = num => parts[num < 0 ? 'negatives' : 'positives'].push(Math.abs(num)); + let range = []; + let index = 0; + + while (descending ? a >= b : a <= b) { + if (options.toRegex === true && step > 1) { + push(a); + } else { + range.push(pad(format(a, index), maxLen, toNumber)); + } + a = descending ? a - step : a + step; + index++; + } + + if (options.toRegex === true) { + return step > 1 + ? toSequence(parts, options) + : toRegex(range, null, { wrap: false, ...options }); + } + + return range; +}; + +const fillLetters = (start, end, step = 1, options = {}) => { + if ((!isNumber(start) && start.length > 1) || (!isNumber(end) && end.length > 1)) { + return invalidRange(start, end, options); + } + + + let format = options.transform || (val => String.fromCharCode(val)); + let a = `${start}`.charCodeAt(0); + let b = `${end}`.charCodeAt(0); + + let descending = a > b; + let min = Math.min(a, b); + let max = Math.max(a, b); + + if (options.toRegex && step === 1) { + return toRange(min, max, false, options); + } + + let range = []; + let index = 0; + + while (descending ? a >= b : a <= b) { + range.push(format(a, index)); + a = descending ? a - step : a + step; + index++; + } + + if (options.toRegex === true) { + return toRegex(range, null, { wrap: false, options }); + } + + return range; +}; + +const fill = (start, end, step, options = {}) => { + if (end == null && isValidValue(start)) { + return [start]; + } + + if (!isValidValue(start) || !isValidValue(end)) { + return invalidRange(start, end, options); + } + + if (typeof step === 'function') { + return fill(start, end, 1, { transform: step }); + } + + if (isObject(step)) { + return fill(start, end, 0, step); + } + + let opts = { ...options }; + if (opts.capture === true) opts.wrap = true; + step = step || opts.step || 1; + + if (!isNumber(step)) { + if (step != null && !isObject(step)) return invalidStep(step, opts); + return fill(start, end, 1, step); + } + + if (isNumber(start) && isNumber(end)) { + return fillNumbers(start, end, step, opts); + } + + return fillLetters(start, end, Math.max(Math.abs(step), 1), opts); +}; + +module.exports = fill; + + +/***/ }), +/* 195 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/*! + * to-regex-range + * + * Copyright (c) 2015-present, Jon Schlinkert. + * Released under the MIT License. + */ + + + +const isNumber = __webpack_require__(196); + +const toRegexRange = (min, max, options) => { + if (isNumber(min) === false) { + throw new TypeError('toRegexRange: expected the first argument to be a number'); + } + + if (max === void 0 || min === max) { + return String(min); + } + + if (isNumber(max) === false) { + throw new TypeError('toRegexRange: expected the second argument to be a number.'); + } + + let opts = { relaxZeros: true, ...options }; + if (typeof opts.strictZeros === 'boolean') { + opts.relaxZeros = opts.strictZeros === false; + } + + let relax = String(opts.relaxZeros); + let shorthand = String(opts.shorthand); + let capture = String(opts.capture); + let wrap = String(opts.wrap); + let cacheKey = min + ':' + max + '=' + relax + shorthand + capture + wrap; + + if (toRegexRange.cache.hasOwnProperty(cacheKey)) { + return toRegexRange.cache[cacheKey].result; + } + + let a = Math.min(min, max); + let b = Math.max(min, max); + + if (Math.abs(a - b) === 1) { + let result = min + '|' + max; + if (opts.capture) { + return `(${result})`; + } + if (opts.wrap === false) { + return result; + } + return `(?:${result})`; + } + + let isPadded = hasPadding(min) || hasPadding(max); + let state = { min, max, a, b }; + let positives = []; + let negatives = []; + + if (isPadded) { + state.isPadded = isPadded; + state.maxLen = String(state.max).length; + } + + if (a < 0) { + let newMin = b < 0 ? Math.abs(b) : 1; + negatives = splitToPatterns(newMin, Math.abs(a), state, opts); + a = state.a = 0; + } + + if (b >= 0) { + positives = splitToPatterns(a, b, state, opts); + } + + state.negatives = negatives; + state.positives = positives; + state.result = collatePatterns(negatives, positives, opts); + + if (opts.capture === true) { + state.result = `(${state.result})`; + } else if (opts.wrap !== false && (positives.length + negatives.length) > 1) { + state.result = `(?:${state.result})`; + } + + toRegexRange.cache[cacheKey] = state; + return state.result; +}; + +function collatePatterns(neg, pos, options) { + let onlyNegative = filterPatterns(neg, pos, '-', false, options) || []; + let onlyPositive = filterPatterns(pos, neg, '', false, options) || []; + let intersected = filterPatterns(neg, pos, '-?', true, options) || []; + let subpatterns = onlyNegative.concat(intersected).concat(onlyPositive); + return subpatterns.join('|'); +} + +function splitToRanges(min, max) { + let nines = 1; + let zeros = 1; + + let stop = countNines(min, nines); + let stops = new Set([max]); + + while (min <= stop && stop <= max) { + stops.add(stop); + nines += 1; + stop = countNines(min, nines); + } + + stop = countZeros(max + 1, zeros) - 1; + + while (min < stop && stop <= max) { + stops.add(stop); + zeros += 1; + stop = countZeros(max + 1, zeros) - 1; + } + + stops = [...stops]; + stops.sort(compare); + return stops; +} + +/** + * Convert a range to a regex pattern + * @param {Number} `start` + * @param {Number} `stop` + * @return {String} + */ + +function rangeToPattern(start, stop, options) { + if (start === stop) { + return { pattern: start, count: [], digits: 0 }; + } + + let zipped = zip(start, stop); + let digits = zipped.length; + let pattern = ''; + let count = 0; + + for (let i = 0; i < digits; i++) { + let [startDigit, stopDigit] = zipped[i]; + + if (startDigit === stopDigit) { + pattern += startDigit; + + } else if (startDigit !== '0' || stopDigit !== '9') { + pattern += toCharacterClass(startDigit, stopDigit, options); + + } else { + count++; + } + } + + if (count) { + pattern += options.shorthand === true ? '\\d' : '[0-9]'; + } + + return { pattern, count: [count], digits }; +} + +function splitToPatterns(min, max, tok, options) { + let ranges = splitToRanges(min, max); + let tokens = []; + let start = min; + let prev; + + for (let i = 0; i < ranges.length; i++) { + let max = ranges[i]; + let obj = rangeToPattern(String(start), String(max), options); + let zeros = ''; + + if (!tok.isPadded && prev && prev.pattern === obj.pattern) { + if (prev.count.length > 1) { + prev.count.pop(); + } + + prev.count.push(obj.count[0]); + prev.string = prev.pattern + toQuantifier(prev.count); + start = max + 1; + continue; + } + + if (tok.isPadded) { + zeros = padZeros(max, tok, options); + } + + obj.string = zeros + obj.pattern + toQuantifier(obj.count); + tokens.push(obj); + start = max + 1; + prev = obj; + } + + return tokens; +} + +function filterPatterns(arr, comparison, prefix, intersection, options) { + let result = []; + + for (let ele of arr) { + let { string } = ele; + + // only push if _both_ are negative... + if (!intersection && !contains(comparison, 'string', string)) { + result.push(prefix + string); + } + + // or _both_ are positive + if (intersection && contains(comparison, 'string', string)) { + result.push(prefix + string); + } + } + return result; +} + +/** + * Zip strings + */ + +function zip(a, b) { + let arr = []; + for (let i = 0; i < a.length; i++) arr.push([a[i], b[i]]); + return arr; +} + +function compare(a, b) { + return a > b ? 1 : b > a ? -1 : 0; +} + +function contains(arr, key, val) { + return arr.some(ele => ele[key] === val); +} + +function countNines(min, len) { + return Number(String(min).slice(0, -len) + '9'.repeat(len)); +} + +function countZeros(integer, zeros) { + return integer - (integer % Math.pow(10, zeros)); +} + +function toQuantifier(digits) { + let [start = 0, stop = ''] = digits; + if (stop || start > 1) { + return `{${start + (stop ? ',' + stop : '')}}`; + } + return ''; +} + +function toCharacterClass(a, b, options) { + return `[${a}${(b - a === 1) ? '' : '-'}${b}]`; +} + +function hasPadding(str) { + return /^-?(0+)\d/.test(str); +} + +function padZeros(value, tok, options) { + if (!tok.isPadded) { + return value; + } + + let diff = Math.abs(tok.maxLen - String(value).length); + let relax = options.relaxZeros !== false; + + switch (diff) { + case 0: + return ''; + case 1: + return relax ? '0?' : '0'; + case 2: + return relax ? '0{0,2}' : '00'; + default: { + return relax ? `0{0,${diff}}` : `0{${diff}}`; + } + } +} + +/** + * Cache + */ + +toRegexRange.cache = {}; +toRegexRange.clearCache = () => (toRegexRange.cache = {}); + +/** + * Expose `toRegexRange` + */ + +module.exports = toRegexRange; + + +/***/ }), +/* 196 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/*! + * is-number + * + * Copyright (c) 2014-present, Jon Schlinkert. + * Released under the MIT License. + */ + + + +module.exports = function(num) { + if (typeof num === 'number') { + return num - num === 0; + } + if (typeof num === 'string' && num.trim() !== '') { + return Number.isFinite ? Number.isFinite(+num) : isFinite(+num); + } + return false; +}; + + +/***/ }), +/* 197 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const fill = __webpack_require__(194); +const stringify = __webpack_require__(191); +const utils = __webpack_require__(192); + +const append = (queue = '', stash = '', enclose = false) => { + let result = []; + + queue = [].concat(queue); + stash = [].concat(stash); + + if (!stash.length) return queue; + if (!queue.length) { + return enclose ? utils.flatten(stash).map(ele => `{${ele}}`) : stash; + } + + for (let item of queue) { + if (Array.isArray(item)) { + for (let value of item) { + result.push(append(value, stash, enclose)); + } + } else { + for (let ele of stash) { + if (enclose === true && typeof ele === 'string') ele = `{${ele}}`; + result.push(Array.isArray(ele) ? append(item, ele, enclose) : (item + ele)); + } + } + } + return utils.flatten(result); +}; + +const expand = (ast, options = {}) => { + let rangeLimit = options.rangeLimit === void 0 ? 1000 : options.rangeLimit; + + let walk = (node, parent = {}) => { + node.queue = []; + + let p = parent; + let q = parent.queue; + + while (p.type !== 'brace' && p.type !== 'root' && p.parent) { + p = p.parent; + q = p.queue; + } + + if (node.invalid || node.dollar) { + q.push(append(q.pop(), stringify(node, options))); + return; + } + + if (node.type === 'brace' && node.invalid !== true && node.nodes.length === 2) { + q.push(append(q.pop(), ['{}'])); + return; + } + + if (node.nodes && node.ranges > 0) { + let args = utils.reduce(node.nodes); + + if (utils.exceedsLimit(...args, options.step, rangeLimit)) { + throw new RangeError('expanded array length exceeds range limit. Use options.rangeLimit to increase or disable the limit.'); + } + + let range = fill(...args, options); + if (range.length === 0) { + range = stringify(node, options); + } + + q.push(append(q.pop(), range)); + node.nodes = []; + return; + } + + let enclose = utils.encloseBrace(node); + let queue = node.queue; + let block = node; + + while (block.type !== 'brace' && block.type !== 'root' && block.parent) { + block = block.parent; + queue = block.queue; + } + + for (let i = 0; i < node.nodes.length; i++) { + let child = node.nodes[i]; + + if (child.type === 'comma' && node.type === 'brace') { + if (i === 1) queue.push(''); + queue.push(''); + continue; + } + + if (child.type === 'close') { + q.push(append(q.pop(), queue, enclose)); + continue; + } + + if (child.value && child.type !== 'open') { + queue.push(append(queue.pop(), child.value)); + continue; + } + + if (child.nodes) { + walk(child, node); + } + } + + return queue; + }; + + return utils.flatten(walk(ast)); +}; + +module.exports = expand; + + +/***/ }), +/* 198 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const stringify = __webpack_require__(191); + +/** + * Constants + */ + +const { + MAX_LENGTH, + CHAR_BACKSLASH, /* \ */ + CHAR_BACKTICK, /* ` */ + CHAR_COMMA, /* , */ + CHAR_DOT, /* . */ + CHAR_LEFT_PARENTHESES, /* ( */ + CHAR_RIGHT_PARENTHESES, /* ) */ + CHAR_LEFT_CURLY_BRACE, /* { */ + CHAR_RIGHT_CURLY_BRACE, /* } */ + CHAR_LEFT_SQUARE_BRACKET, /* [ */ + CHAR_RIGHT_SQUARE_BRACKET, /* ] */ + CHAR_DOUBLE_QUOTE, /* " */ + CHAR_SINGLE_QUOTE, /* ' */ + CHAR_NO_BREAK_SPACE, + CHAR_ZERO_WIDTH_NOBREAK_SPACE +} = __webpack_require__(199); + +/** + * parse + */ + +const parse = (input, options = {}) => { + if (typeof input !== 'string') { + throw new TypeError('Expected a string'); + } + + let opts = options || {}; + let max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH; + if (input.length > max) { + throw new SyntaxError(`Input length (${input.length}), exceeds max characters (${max})`); + } + + let ast = { type: 'root', input, nodes: [] }; + let stack = [ast]; + let block = ast; + let prev = ast; + let brackets = 0; + let length = input.length; + let index = 0; + let depth = 0; + let value; + let memo = {}; + + /** + * Helpers + */ + + const advance = () => input[index++]; + const push = node => { + if (node.type === 'text' && prev.type === 'dot') { + prev.type = 'text'; + } + + if (prev && prev.type === 'text' && node.type === 'text') { + prev.value += node.value; + return; + } + + block.nodes.push(node); + node.parent = block; + node.prev = prev; + prev = node; + return node; + }; + + push({ type: 'bos' }); + + while (index < length) { + block = stack[stack.length - 1]; + value = advance(); + + /** + * Invalid chars + */ + + if (value === CHAR_ZERO_WIDTH_NOBREAK_SPACE || value === CHAR_NO_BREAK_SPACE) { + continue; + } + + /** + * Escaped chars + */ + + if (value === CHAR_BACKSLASH) { + push({ type: 'text', value: (options.keepEscaping ? value : '') + advance() }); + continue; + } + + /** + * Right square bracket (literal): ']' + */ + + if (value === CHAR_RIGHT_SQUARE_BRACKET) { + push({ type: 'text', value: '\\' + value }); + continue; + } + + /** + * Left square bracket: '[' + */ + + if (value === CHAR_LEFT_SQUARE_BRACKET) { + brackets++; + + let closed = true; + let next; + + while (index < length && (next = advance())) { + value += next; + + if (next === CHAR_LEFT_SQUARE_BRACKET) { + brackets++; + continue; + } + + if (next === CHAR_BACKSLASH) { + value += advance(); + continue; + } + + if (next === CHAR_RIGHT_SQUARE_BRACKET) { + brackets--; + + if (brackets === 0) { + break; + } + } + } + + push({ type: 'text', value }); + continue; + } + + /** + * Parentheses + */ + + if (value === CHAR_LEFT_PARENTHESES) { + block = push({ type: 'paren', nodes: [] }); + stack.push(block); + push({ type: 'text', value }); + continue; + } + + if (value === CHAR_RIGHT_PARENTHESES) { + if (block.type !== 'paren') { + push({ type: 'text', value }); + continue; + } + block = stack.pop(); + push({ type: 'text', value }); + block = stack[stack.length - 1]; + continue; + } + + /** + * Quotes: '|"|` + */ + + if (value === CHAR_DOUBLE_QUOTE || value === CHAR_SINGLE_QUOTE || value === CHAR_BACKTICK) { + let open = value; + let next; + + if (options.keepQuotes !== true) { + value = ''; + } + + while (index < length && (next = advance())) { + if (next === CHAR_BACKSLASH) { + value += next + advance(); + continue; + } + + if (next === open) { + if (options.keepQuotes === true) value += next; + break; + } + + value += next; + } + + push({ type: 'text', value }); + continue; + } + + /** + * Left curly brace: '{' + */ + + if (value === CHAR_LEFT_CURLY_BRACE) { + depth++; + + let dollar = prev.value && prev.value.slice(-1) === '$' || block.dollar === true; + let brace = { + type: 'brace', + open: true, + close: false, + dollar, + depth, + commas: 0, + ranges: 0, + nodes: [] + }; + + block = push(brace); + stack.push(block); + push({ type: 'open', value }); + continue; + } + + /** + * Right curly brace: '}' + */ + + if (value === CHAR_RIGHT_CURLY_BRACE) { + if (block.type !== 'brace') { + push({ type: 'text', value }); + continue; + } + + let type = 'close'; + block = stack.pop(); + block.close = true; + + push({ type, value }); + depth--; + + block = stack[stack.length - 1]; + continue; + } + + /** + * Comma: ',' + */ + + if (value === CHAR_COMMA && depth > 0) { + if (block.ranges > 0) { + block.ranges = 0; + let open = block.nodes.shift(); + block.nodes = [open, { type: 'text', value: stringify(block) }]; + } + + push({ type: 'comma', value }); + block.commas++; + continue; + } + + /** + * Dot: '.' + */ + + if (value === CHAR_DOT && depth > 0 && block.commas === 0) { + let siblings = block.nodes; + + if (depth === 0 || siblings.length === 0) { + push({ type: 'text', value }); + continue; + } + + if (prev.type === 'dot') { + block.range = []; + prev.value += value; + prev.type = 'range'; + + if (block.nodes.length !== 3 && block.nodes.length !== 5) { + block.invalid = true; + block.ranges = 0; + prev.type = 'text'; + continue; + } + + block.ranges++; + block.args = []; + continue; + } + + if (prev.type === 'range') { + siblings.pop(); + + let before = siblings[siblings.length - 1]; + before.value += prev.value + value; + prev = before; + block.ranges--; + continue; + } + + push({ type: 'dot', value }); + continue; + } + + /** + * Text + */ + + push({ type: 'text', value }); + } + + // Mark imbalanced braces and brackets as invalid + do { + block = stack.pop(); + + if (block.type !== 'root') { + block.nodes.forEach(node => { + if (!node.nodes) { + if (node.type === 'open') node.isOpen = true; + if (node.type === 'close') node.isClose = true; + if (!node.nodes) node.type = 'text'; + node.invalid = true; + } + }); + + // get the location of the block on parent.nodes (block's siblings) + let parent = stack[stack.length - 1]; + let index = parent.nodes.indexOf(block); + // replace the (invalid) block with it's nodes + parent.nodes.splice(index, 1, ...block.nodes); + } + } while (stack.length > 0); + + push({ type: 'eos' }); + return ast; +}; + +module.exports = parse; + + +/***/ }), +/* 199 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +module.exports = { + MAX_LENGTH: 1024 * 64, + + // Digits + CHAR_0: '0', /* 0 */ + CHAR_9: '9', /* 9 */ + + // Alphabet chars. + CHAR_UPPERCASE_A: 'A', /* A */ + CHAR_LOWERCASE_A: 'a', /* a */ + CHAR_UPPERCASE_Z: 'Z', /* Z */ + CHAR_LOWERCASE_Z: 'z', /* z */ + + CHAR_LEFT_PARENTHESES: '(', /* ( */ + CHAR_RIGHT_PARENTHESES: ')', /* ) */ + + CHAR_ASTERISK: '*', /* * */ + + // Non-alphabetic chars. + CHAR_AMPERSAND: '&', /* & */ + CHAR_AT: '@', /* @ */ + CHAR_BACKSLASH: '\\', /* \ */ + CHAR_BACKTICK: '`', /* ` */ + CHAR_CARRIAGE_RETURN: '\r', /* \r */ + CHAR_CIRCUMFLEX_ACCENT: '^', /* ^ */ + CHAR_COLON: ':', /* : */ + CHAR_COMMA: ',', /* , */ + CHAR_DOLLAR: '$', /* . */ + CHAR_DOT: '.', /* . */ + CHAR_DOUBLE_QUOTE: '"', /* " */ + CHAR_EQUAL: '=', /* = */ + CHAR_EXCLAMATION_MARK: '!', /* ! */ + CHAR_FORM_FEED: '\f', /* \f */ + CHAR_FORWARD_SLASH: '/', /* / */ + CHAR_HASH: '#', /* # */ + CHAR_HYPHEN_MINUS: '-', /* - */ + CHAR_LEFT_ANGLE_BRACKET: '<', /* < */ + CHAR_LEFT_CURLY_BRACE: '{', /* { */ + CHAR_LEFT_SQUARE_BRACKET: '[', /* [ */ + CHAR_LINE_FEED: '\n', /* \n */ + CHAR_NO_BREAK_SPACE: '\u00A0', /* \u00A0 */ + CHAR_PERCENT: '%', /* % */ + CHAR_PLUS: '+', /* + */ + CHAR_QUESTION_MARK: '?', /* ? */ + CHAR_RIGHT_ANGLE_BRACKET: '>', /* > */ + CHAR_RIGHT_CURLY_BRACE: '}', /* } */ + CHAR_RIGHT_SQUARE_BRACKET: ']', /* ] */ + CHAR_SEMICOLON: ';', /* ; */ + CHAR_SINGLE_QUOTE: '\'', /* ' */ + CHAR_SPACE: ' ', /* */ + CHAR_TAB: '\t', /* \t */ + CHAR_UNDERSCORE: '_', /* _ */ + CHAR_VERTICAL_LINE: '|', /* | */ + CHAR_ZERO_WIDTH_NOBREAK_SPACE: '\uFEFF' /* \uFEFF */ +}; + + +/***/ }), +/* 200 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +module.exports = __webpack_require__(201); + + +/***/ }), +/* 201 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const path = __webpack_require__(16); +const scan = __webpack_require__(202); +const parse = __webpack_require__(205); +const utils = __webpack_require__(203); + +/** + * Creates a matcher function from one or more glob patterns. The + * returned function takes a string to match as its first argument, + * and returns true if the string is a match. The returned matcher + * function also takes a boolean as the second argument that, when true, + * returns an object with additional information. + * + * ```js + * const picomatch = require('picomatch'); + * // picomatch(glob[, options]); + * + * const isMatch = picomatch('*.!(*a)'); + * console.log(isMatch('a.a')); //=> false + * console.log(isMatch('a.b')); //=> true + * ``` + * @name picomatch + * @param {String|Array} `globs` One or more glob patterns. + * @param {Object=} `options` + * @return {Function=} Returns a matcher function. + * @api public + */ + +const picomatch = (glob, options, returnState = false) => { + if (Array.isArray(glob)) { + let fns = glob.map(input => picomatch(input, options, returnState)); + return str => { + for (let isMatch of fns) { + let state = isMatch(str); + if (state) return state; + } + return false; + }; + } + + if (typeof glob !== 'string' || glob === '') { + throw new TypeError('Expected pattern to be a non-empty string'); + } + + let opts = options || {}; + let posix = utils.isWindows(options); + let regex = picomatch.makeRe(glob, options, false, true); + let state = regex.state; + delete regex.state; + + let isIgnored = () => false; + if (opts.ignore) { + let ignoreOpts = { ...options, ignore: null, onMatch: null, onResult: null }; + isIgnored = picomatch(opts.ignore, ignoreOpts, returnState); + } + + const matcher = (input, returnObject = false) => { + let { isMatch, match, output } = picomatch.test(input, regex, options, { glob, posix }); + let result = { glob, state, regex, posix, input, output, match, isMatch }; + + if (typeof opts.onResult === 'function') { + opts.onResult(result); + } + + if (isMatch === false) { + result.isMatch = false; + return returnObject ? result : false; + } + + if (isIgnored(input)) { + if (typeof opts.onIgnore === 'function') { + opts.onIgnore(result); + } + result.isMatch = false; + return returnObject ? result : false; + } + + if (typeof opts.onMatch === 'function') { + opts.onMatch(result); + } + return returnObject ? result : true; + }; + + if (returnState) { + matcher.state = state; + } + + return matcher; +}; + +/** + * Test `input` with the given `regex`. This is used by the main + * `picomatch()` function to test the input string. + * + * ```js + * const picomatch = require('picomatch'); + * // picomatch.test(input, regex[, options]); + * + * console.log(picomatch.test('foo/bar', /^(?:([^/]*?)\/([^/]*?))$/)); + * // { isMatch: true, match: [ 'foo/', 'foo', 'bar' ], output: 'foo/bar' } + * ``` + * @param {String} `input` String to test. + * @param {RegExp} `regex` + * @return {Object} Returns an object with matching info. + * @api public + */ + +picomatch.test = (input, regex, options, { glob, posix } = {}) => { + if (typeof input !== 'string') { + throw new TypeError('Expected input to be a string'); + } + + if (input === '') { + return { isMatch: false, output: '' }; + } + + let opts = options || {}; + let format = opts.format || (posix ? utils.toPosixSlashes : null); + let match = input === glob; + let output = (match && format) ? format(input) : input; + + if (match === false) { + output = format ? format(input) : input; + match = output === glob; + } + + if (match === false || opts.capture === true) { + if (opts.matchBase === true || opts.basename === true) { + match = picomatch.matchBase(input, regex, options, posix); + } else { + match = regex.exec(output); + } + } + + return { isMatch: !!match, match, output }; +}; + +/** + * Match the basename of a filepath. + * + * ```js + * const picomatch = require('picomatch'); + * // picomatch.matchBase(input, glob[, options]); + * console.log(picomatch.matchBase('foo/bar.js', '*.js'); // true + * ``` + * @param {String} `input` String to test. + * @param {RegExp|String} `glob` Glob pattern or regex created by [.makeRe](#makeRe). + * @return {Boolean} + * @api public + */ + +picomatch.matchBase = (input, glob, options, posix = utils.isWindows(options)) => { + let regex = glob instanceof RegExp ? glob : picomatch.makeRe(glob, options); + return regex.test(path.basename(input)); +}; + +/** + * Returns true if **any** of the given glob `patterns` match the specified `string`. + * + * ```js + * const picomatch = require('picomatch'); + * // picomatch.isMatch(string, patterns[, options]); + * + * console.log(picomatch.isMatch('a.a', ['b.*', '*.a'])); //=> true + * console.log(picomatch.isMatch('a.a', 'b.*')); //=> false + * ``` + * @param {String|Array} str The string to test. + * @param {String|Array} patterns One or more glob patterns to use for matching. + * @param {Object} [options] See available [options](#options). + * @return {Boolean} Returns true if any patterns match `str` + * @api public + */ + +picomatch.isMatch = (str, patterns, options) => picomatch(patterns, options)(str); + +/** + * Parse a glob pattern to create the source string for a regular + * expression. + * + * ```js + * const picomatch = require('picomatch'); + * const result = picomatch.parse(glob[, options]); + * ``` + * @param {String} `glob` + * @param {Object} `options` + * @return {Object} Returns an object with useful properties and output to be used as a regex source string. + * @api public + */ + +picomatch.parse = (glob, options) => parse(glob, options); + +/** + * Scan a glob pattern to separate the pattern into segments. + * + * ```js + * const picomatch = require('picomatch'); + * // picomatch.scan(input[, options]); + * + * const result = picomatch.scan('!./foo/*.js'); + * console.log(result); + * // { prefix: '!./', + * // input: '!./foo/*.js', + * // base: 'foo', + * // glob: '*.js', + * // negated: true, + * // isGlob: true } + * ``` + * @param {String} `input` Glob pattern to scan. + * @param {Object} `options` + * @return {Object} Returns an object with + * @api public + */ + +picomatch.scan = (input, options) => scan(input, options); + +/** + * Create a regular expression from a glob pattern. + * + * ```js + * const picomatch = require('picomatch'); + * // picomatch.makeRe(input[, options]); + * + * console.log(picomatch.makeRe('*.js')); + * //=> /^(?:(?!\.)(?=.)[^/]*?\.js)$/ + * ``` + * @param {String} `input` A glob pattern to convert to regex. + * @param {Object} `options` + * @return {RegExp} Returns a regex created from the given pattern. + * @api public + */ + +picomatch.makeRe = (input, options, returnOutput = false, returnState = false) => { + if (!input || typeof input !== 'string') { + throw new TypeError('Expected a non-empty string'); + } + + let opts = options || {}; + let prepend = opts.contains ? '' : '^'; + let append = opts.contains ? '' : '$'; + let state = { negated: false, fastpaths: true }; + let prefix = ''; + let output; + + if (input.startsWith('./')) { + input = input.slice(2); + prefix = state.prefix = './'; + } + + if (opts.fastpaths !== false && (input[0] === '.' || input[0] === '*')) { + output = parse.fastpaths(input, options); + } + + if (output === void 0) { + state = picomatch.parse(input, options); + state.prefix = prefix + (state.prefix || ''); + output = state.output; + } + + if (returnOutput === true) { + return output; + } + + let source = `${prepend}(?:${output})${append}`; + if (state && state.negated === true) { + source = `^(?!${source}).*$`; + } + + let regex = picomatch.toRegex(source, options); + if (returnState === true) { + regex.state = state; + } + + return regex; +}; + +/** + * Create a regular expression from the given regex source string. + * + * ```js + * const picomatch = require('picomatch'); + * // picomatch.toRegex(source[, options]); + * + * const { output } = picomatch.parse('*.js'); + * console.log(picomatch.toRegex(output)); + * //=> /^(?:(?!\.)(?=.)[^/]*?\.js)$/ + * ``` + * @param {String} `source` Regular expression source string. + * @param {Object} `options` + * @return {RegExp} + * @api public + */ + +picomatch.toRegex = (source, options) => { + try { + let opts = options || {}; + return new RegExp(source, opts.flags || (opts.nocase ? 'i' : '')); + } catch (err) { + if (options && options.debug === true) throw err; + return /$^/; + } +}; + +/** + * Picomatch constants. + * @return {Object} + */ + +picomatch.constants = __webpack_require__(204); + +/** + * Expose "picomatch" + */ + +module.exports = picomatch; + + +/***/ }), +/* 202 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const utils = __webpack_require__(203); + +const { + CHAR_ASTERISK, /* * */ + CHAR_AT, /* @ */ + CHAR_BACKWARD_SLASH, /* \ */ + CHAR_COMMA, /* , */ + CHAR_DOT, /* . */ + CHAR_EXCLAMATION_MARK, /* ! */ + CHAR_FORWARD_SLASH, /* / */ + CHAR_LEFT_CURLY_BRACE, /* { */ + CHAR_LEFT_PARENTHESES, /* ( */ + CHAR_LEFT_SQUARE_BRACKET, /* [ */ + CHAR_PLUS, /* + */ + CHAR_QUESTION_MARK, /* ? */ + CHAR_RIGHT_CURLY_BRACE, /* } */ + CHAR_RIGHT_PARENTHESES, /* ) */ + CHAR_RIGHT_SQUARE_BRACKET /* ] */ +} = __webpack_require__(204); + +const isPathSeparator = code => { + return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH; +}; + +/** + * Quickly scans a glob pattern and returns an object with a handful of + * useful properties, like `isGlob`, `path` (the leading non-glob, if it exists), + * `glob` (the actual pattern), and `negated` (true if the path starts with `!`). + * + * ```js + * const pm = require('picomatch'); + * console.log(pm.scan('foo/bar/*.js')); + * { isGlob: true, input: 'foo/bar/*.js', base: 'foo/bar', glob: '*.js' } + * ``` + * @param {String} `str` + * @param {Object} `options` + * @return {Object} Returns an object with tokens and regex source string. + * @api public + */ + +module.exports = (input, options) => { + let opts = options || {}; + let length = input.length - 1; + let index = -1; + let start = 0; + let lastIndex = 0; + let isGlob = false; + let backslashes = false; + let negated = false; + let braces = 0; + let prev; + let code; + + let braceEscaped = false; + + let eos = () => index >= length; + let advance = () => { + prev = code; + return input.charCodeAt(++index); + }; + + while (index < length) { + code = advance(); + let next; + + if (code === CHAR_BACKWARD_SLASH) { + backslashes = true; + next = advance(); + + if (next === CHAR_LEFT_CURLY_BRACE) { + braceEscaped = true; + } + continue; + } + + if (braceEscaped === true || code === CHAR_LEFT_CURLY_BRACE) { + braces++; + + while (!eos() && (next = advance())) { + if (next === CHAR_BACKWARD_SLASH) { + backslashes = true; + next = advance(); + continue; + } + + if (next === CHAR_LEFT_CURLY_BRACE) { + braces++; + continue; + } + + if (!braceEscaped && next === CHAR_DOT && (next = advance()) === CHAR_DOT) { + isGlob = true; + break; + } + + if (!braceEscaped && next === CHAR_COMMA) { + isGlob = true; + break; + } + + if (next === CHAR_RIGHT_CURLY_BRACE) { + braces--; + if (braces === 0) { + braceEscaped = false; + break; + } + } + } + } + + if (code === CHAR_FORWARD_SLASH) { + if (prev === CHAR_DOT && index === (start + 1)) { + start += 2; + continue; + } + + lastIndex = index + 1; + continue; + } + + if (code === CHAR_ASTERISK) { + isGlob = true; + break; + } + + if (code === CHAR_ASTERISK || code === CHAR_QUESTION_MARK) { + isGlob = true; + break; + } + + if (code === CHAR_LEFT_SQUARE_BRACKET) { + while (!eos() && (next = advance())) { + if (next === CHAR_BACKWARD_SLASH) { + backslashes = true; + next = advance(); + continue; + } + + if (next === CHAR_RIGHT_SQUARE_BRACKET) { + isGlob = true; + break; + } + } + } + + let isExtglobChar = code === CHAR_PLUS + || code === CHAR_AT + || code === CHAR_EXCLAMATION_MARK; + + if (isExtglobChar && input.charCodeAt(index + 1) === CHAR_LEFT_PARENTHESES) { + isGlob = true; + break; + } + + if (code === CHAR_EXCLAMATION_MARK && index === start) { + negated = true; + start++; + continue; + } + + if (code === CHAR_LEFT_PARENTHESES) { + while (!eos() && (next = advance())) { + if (next === CHAR_BACKWARD_SLASH) { + backslashes = true; + next = advance(); + continue; + } + + if (next === CHAR_RIGHT_PARENTHESES) { + isGlob = true; + break; + } + } + } + + if (isGlob) { + break; + } + } + + let prefix = ''; + let orig = input; + let base = input; + let glob = ''; + + if (start > 0) { + prefix = input.slice(0, start); + input = input.slice(start); + lastIndex -= start; + } + + if (base && isGlob === true && lastIndex > 0) { + base = input.slice(0, lastIndex); + glob = input.slice(lastIndex); + } else if (isGlob === true) { + base = ''; + glob = input; + } else { + base = input; + } + + if (base && base !== '' && base !== '/' && base !== input) { + if (isPathSeparator(base.charCodeAt(base.length - 1))) { + base = base.slice(0, -1); + } + } + + if (opts.unescape === true) { + if (glob) glob = utils.removeBackslashes(glob); + + if (base && backslashes === true) { + base = utils.removeBackslashes(base); + } + } + + return { prefix, input: orig, base, glob, negated, isGlob }; +}; + + +/***/ }), +/* 203 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const path = __webpack_require__(16); +const win32 = process.platform === 'win32'; +const { + REGEX_SPECIAL_CHARS, + REGEX_SPECIAL_CHARS_GLOBAL, + REGEX_REMOVE_BACKSLASH +} = __webpack_require__(204); + +exports.isObject = val => val !== null && typeof val === 'object' && !Array.isArray(val); +exports.hasRegexChars = str => REGEX_SPECIAL_CHARS.test(str); +exports.isRegexChar = str => str.length === 1 && exports.hasRegexChars(str); +exports.escapeRegex = str => str.replace(REGEX_SPECIAL_CHARS_GLOBAL, '\\$1'); +exports.toPosixSlashes = str => str.replace(/\\/g, '/'); + +exports.removeBackslashes = str => { + return str.replace(REGEX_REMOVE_BACKSLASH, match => { + return match === '\\' ? '' : match; + }); +} + +exports.supportsLookbehinds = () => { + let segs = process.version.slice(1).split('.'); + if (segs.length === 3 && +segs[0] >= 9 || (+segs[0] === 8 && +segs[1] >= 10)) { + return true; + } + return false; +}; + +exports.isWindows = options => { + if (options && typeof options.windows === 'boolean') { + return options.windows; + } + return win32 === true || path.sep === '\\'; +}; + +exports.escapeLast = (input, char, lastIdx) => { + let idx = input.lastIndexOf(char, lastIdx); + if (idx === -1) return input; + if (input[idx - 1] === '\\') return exports.escapeLast(input, char, idx - 1); + return input.slice(0, idx) + '\\' + input.slice(idx); +}; + + +/***/ }), +/* 204 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const path = __webpack_require__(16); +const WIN_SLASH = '\\\\/'; +const WIN_NO_SLASH = `[^${WIN_SLASH}]`; + +/** + * Posix glob regex + */ + +const DOT_LITERAL = '\\.'; +const PLUS_LITERAL = '\\+'; +const QMARK_LITERAL = '\\?'; +const SLASH_LITERAL = '\\/'; +const ONE_CHAR = '(?=.)'; +const QMARK = '[^/]'; +const END_ANCHOR = `(?:${SLASH_LITERAL}|$)`; +const START_ANCHOR = `(?:^|${SLASH_LITERAL})`; +const DOTS_SLASH = `${DOT_LITERAL}{1,2}${END_ANCHOR}`; +const NO_DOT = `(?!${DOT_LITERAL})`; +const NO_DOTS = `(?!${START_ANCHOR}${DOTS_SLASH})`; +const NO_DOT_SLASH = `(?!${DOT_LITERAL}{0,1}${END_ANCHOR})`; +const NO_DOTS_SLASH = `(?!${DOTS_SLASH})`; +const QMARK_NO_DOT = `[^.${SLASH_LITERAL}]`; +const STAR = `${QMARK}*?`; + +const POSIX_CHARS = { + DOT_LITERAL, + PLUS_LITERAL, + QMARK_LITERAL, + SLASH_LITERAL, + ONE_CHAR, + QMARK, + END_ANCHOR, + DOTS_SLASH, + NO_DOT, + NO_DOTS, + NO_DOT_SLASH, + NO_DOTS_SLASH, + QMARK_NO_DOT, + STAR, + START_ANCHOR +}; + +/** + * Windows glob regex + */ + +const WINDOWS_CHARS = { + ...POSIX_CHARS, + + SLASH_LITERAL: `[${WIN_SLASH}]`, + QMARK: WIN_NO_SLASH, + STAR: `${WIN_NO_SLASH}*?`, + DOTS_SLASH: `${DOT_LITERAL}{1,2}(?:[${WIN_SLASH}]|$)`, + NO_DOT: `(?!${DOT_LITERAL})`, + NO_DOTS: `(?!(?:^|[${WIN_SLASH}])${DOT_LITERAL}{1,2}(?:[${WIN_SLASH}]|$))`, + NO_DOT_SLASH: `(?!${DOT_LITERAL}{0,1}(?:[${WIN_SLASH}]|$))`, + NO_DOTS_SLASH: `(?!${DOT_LITERAL}{1,2}(?:[${WIN_SLASH}]|$))`, + QMARK_NO_DOT: `[^.${WIN_SLASH}]`, + START_ANCHOR: `(?:^|[${WIN_SLASH}])`, + END_ANCHOR: `(?:[${WIN_SLASH}]|$)` +}; + +/** + * POSIX Bracket Regex + */ + +const POSIX_REGEX_SOURCE = { + alnum: 'a-zA-Z0-9', + alpha: 'a-zA-Z', + ascii: '\\x00-\\x7F', + blank: ' \\t', + cntrl: '\\x00-\\x1F\\x7F', + digit: '0-9', + graph: '\\x21-\\x7E', + lower: 'a-z', + print: '\\x20-\\x7E ', + punct: '\\-!"#$%&\'()\\*+,./:;<=>?@[\\]^_`{|}~', + space: ' \\t\\r\\n\\v\\f', + upper: 'A-Z', + word: 'A-Za-z0-9_', + xdigit: 'A-Fa-f0-9' +}; + +module.exports = { + MAX_LENGTH: 1024 * 64, + POSIX_REGEX_SOURCE, + + // regular expressions + REGEX_BACKSLASH: /\\(?![*+?^${}(|)[\]])/g, + REGEX_NON_SPECIAL_CHAR: /^[^@![\].,$*+?^{}()|\\/]+/, + REGEX_SPECIAL_CHARS: /[-*+?.^${}(|)[\]]/, + REGEX_SPECIAL_CHARS_BACKREF: /(\\?)((\W)(\3*))/g, + REGEX_SPECIAL_CHARS_GLOBAL: /([-*+?.^${}(|)[\]])/g, + REGEX_REMOVE_BACKSLASH: /(?:\[.*?[^\\]\]|\\(?=.))/g, + + // Replace globs with equivalent patterns to reduce parsing time. + REPLACEMENTS: { + '***': '*', + '**/**': '**', + '**/**/**': '**' + }, + + // Digits + CHAR_0: 48, /* 0 */ + CHAR_9: 57, /* 9 */ + + // Alphabet chars. + CHAR_UPPERCASE_A: 65, /* A */ + CHAR_LOWERCASE_A: 97, /* a */ + CHAR_UPPERCASE_Z: 90, /* Z */ + CHAR_LOWERCASE_Z: 122, /* z */ + + CHAR_LEFT_PARENTHESES: 40, /* ( */ + CHAR_RIGHT_PARENTHESES: 41, /* ) */ + + CHAR_ASTERISK: 42, /* * */ + + // Non-alphabetic chars. + CHAR_AMPERSAND: 38, /* & */ + CHAR_AT: 64, /* @ */ + CHAR_BACKWARD_SLASH: 92, /* \ */ + CHAR_CARRIAGE_RETURN: 13, /* \r */ + CHAR_CIRCUMFLEX_ACCENT: 94, /* ^ */ + CHAR_COLON: 58, /* : */ + CHAR_COMMA: 44, /* , */ + CHAR_DOT: 46, /* . */ + CHAR_DOUBLE_QUOTE: 34, /* " */ + CHAR_EQUAL: 61, /* = */ + CHAR_EXCLAMATION_MARK: 33, /* ! */ + CHAR_FORM_FEED: 12, /* \f */ + CHAR_FORWARD_SLASH: 47, /* / */ + CHAR_GRAVE_ACCENT: 96, /* ` */ + CHAR_HASH: 35, /* # */ + CHAR_HYPHEN_MINUS: 45, /* - */ + CHAR_LEFT_ANGLE_BRACKET: 60, /* < */ + CHAR_LEFT_CURLY_BRACE: 123, /* { */ + CHAR_LEFT_SQUARE_BRACKET: 91, /* [ */ + CHAR_LINE_FEED: 10, /* \n */ + CHAR_NO_BREAK_SPACE: 160, /* \u00A0 */ + CHAR_PERCENT: 37, /* % */ + CHAR_PLUS: 43, /* + */ + CHAR_QUESTION_MARK: 63, /* ? */ + CHAR_RIGHT_ANGLE_BRACKET: 62, /* > */ + CHAR_RIGHT_CURLY_BRACE: 125, /* } */ + CHAR_RIGHT_SQUARE_BRACKET: 93, /* ] */ + CHAR_SEMICOLON: 59, /* ; */ + CHAR_SINGLE_QUOTE: 39, /* ' */ + CHAR_SPACE: 32, /* */ + CHAR_TAB: 9, /* \t */ + CHAR_UNDERSCORE: 95, /* _ */ + CHAR_VERTICAL_LINE: 124, /* | */ + CHAR_ZERO_WIDTH_NOBREAK_SPACE: 65279, /* \uFEFF */ + + SEP: path.sep, + + /** + * Create EXTGLOB_CHARS + */ + + extglobChars(chars) { + return { + '!': { type: 'negate', open: '(?:(?!(?:', close: `))${chars.STAR})` }, + '?': { type: 'qmark', open: '(?:', close: ')?' }, + '+': { type: 'plus', open: '(?:', close: ')+' }, + '*': { type: 'star', open: '(?:', close: ')*' }, + '@': { type: 'at', open: '(?:', close: ')' } + }; + }, + + /** + * Create GLOB_CHARS + */ + + globChars(win32) { + return win32 === true ? WINDOWS_CHARS : POSIX_CHARS; + } +}; + + +/***/ }), +/* 205 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const utils = __webpack_require__(203); +const constants = __webpack_require__(204); + +/** + * Constants + */ + +const { + MAX_LENGTH, + POSIX_REGEX_SOURCE, + REGEX_NON_SPECIAL_CHAR, + REGEX_SPECIAL_CHARS_BACKREF, + REPLACEMENTS +} = constants; + +/** + * Helpers + */ + +const expandRange = (args, options) => { + if (typeof options.expandRange === 'function') { + return options.expandRange(...args, options); + } + + args.sort(); + let value = `[${args.join('-')}]`; + + try { + /* eslint-disable no-new */ + new RegExp(value); + } catch (ex) { + return args.map(v => utils.escapeRegex(v)).join('..'); + } + + return value; +}; + +const negate = state => { + let count = 1; + + while (state.peek() === '!' && (state.peek(2) !== '(' || state.peek(3) === '?')) { + state.advance(); + state.start++; + count++; + } + + if (count % 2 === 0) { + return false; + } + + state.negated = true; + state.start++; + return true; +}; + +/** + * Create the message for a syntax error + */ + +const syntaxError = (type, char) => { + return `Missing ${type}: "${char}" - use "\\\\${char}" to match literal characters`; +}; + +/** + * Parse the given input string. + * @param {String} input + * @param {Object} options + * @return {Object} + */ + +const parse = (input, options) => { + if (typeof input !== 'string') { + throw new TypeError('Expected a string'); + } + + input = REPLACEMENTS[input] || input; + + let opts = { ...options }; + let max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH; + let len = input.length; + if (len > max) { + throw new SyntaxError(`Input length: ${len}, exceeds maximum allowed length: ${max}`); + } + + let bos = { type: 'bos', value: '', output: opts.prepend || '' }; + let tokens = [bos]; + + let capture = opts.capture ? '' : '?:'; + let win32 = utils.isWindows(options); + + // create constants based on platform, for windows or posix + const PLATFORM_CHARS = constants.globChars(win32); + const EXTGLOB_CHARS = constants.extglobChars(PLATFORM_CHARS); + + const { + DOT_LITERAL, + PLUS_LITERAL, + SLASH_LITERAL, + ONE_CHAR, + DOTS_SLASH, + NO_DOT, + NO_DOT_SLASH, + NO_DOTS_SLASH, + QMARK, + QMARK_NO_DOT, + STAR, + START_ANCHOR + } = PLATFORM_CHARS; + + const globstar = (opts) => { + return `(${capture}(?:(?!${START_ANCHOR}${opts.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`; + }; + + let nodot = opts.dot ? '' : NO_DOT; + let star = opts.bash === true ? globstar(opts) : STAR; + let qmarkNoDot = opts.dot ? QMARK : QMARK_NO_DOT; + + if (opts.capture) { + star = `(${star})`; + } + + // minimatch options support + if (typeof opts.noext === 'boolean') { + opts.noextglob = opts.noext; + } + + let state = { + index: -1, + start: 0, + consumed: '', + output: '', + backtrack: false, + brackets: 0, + braces: 0, + parens: 0, + quotes: 0, + tokens + }; + + let extglobs = []; + let stack = []; + let prev = bos; + let value; + + /** + * Tokenizing helpers + */ + + const eos = () => state.index === len - 1; + const peek = state.peek = (n = 1) => input[state.index + n]; + const advance = state.advance = () => input[++state.index]; + const append = token => { + state.output += token.output != null ? token.output : token.value; + state.consumed += token.value || ''; + }; + + const increment = type => { + state[type]++; + stack.push(type); + }; + + const decrement = type => { + state[type]--; + stack.pop(); + }; + + /** + * Push tokens onto the tokens array. This helper speeds up + * tokenizing by 1) helping us avoid backtracking as much as possible, + * and 2) helping us avoid creating extra tokens when consecutive + * characters are plain text. This improves performance and simplifies + * lookbehinds. + */ + + const push = tok => { + if (prev.type === 'globstar') { + let isBrace = state.braces > 0 && (tok.type === 'comma' || tok.type === 'brace'); + let isExtglob = extglobs.length && (tok.type === 'pipe' || tok.type === 'paren'); + if (tok.type !== 'slash' && tok.type !== 'paren' && !isBrace && !isExtglob) { + state.output = state.output.slice(0, -prev.output.length); + prev.type = 'star'; + prev.value = '*'; + prev.output = star; + state.output += prev.output; + } + } + + if (extglobs.length && tok.type !== 'paren' && !EXTGLOB_CHARS[tok.value]) { + extglobs[extglobs.length - 1].inner += tok.value; + } + + if (tok.value || tok.output) append(tok); + if (prev && prev.type === 'text' && tok.type === 'text') { + prev.value += tok.value; + return; + } + + tok.prev = prev; + tokens.push(tok); + prev = tok; + }; + + const extglobOpen = (type, value) => { + let token = { ...EXTGLOB_CHARS[value], conditions: 1, inner: '' }; + + token.prev = prev; + token.parens = state.parens; + token.output = state.output; + let output = (opts.capture ? '(' : '') + token.open; + + push({ type, value, output: state.output ? '' : ONE_CHAR }); + push({ type: 'paren', extglob: true, value: advance(), output }); + increment('parens'); + extglobs.push(token); + }; + + const extglobClose = token => { + let output = token.close + (opts.capture ? ')' : ''); + + if (token.type === 'negate') { + let extglobStar = star; + + if (token.inner && token.inner.length > 1 && token.inner.includes('/')) { + extglobStar = globstar(opts); + } + + if (extglobStar !== star || eos() || /^\)+$/.test(input.slice(state.index + 1))) { + output = token.close = ')$))' + extglobStar; + } + + if (token.prev.type === 'bos' && eos()) { + state.negatedExtglob = true; + } + } + + push({ type: 'paren', extglob: true, value, output }); + decrement('parens'); + }; + + if (opts.fastpaths !== false && !/(^[*!]|[/{[()\]}"])/.test(input)) { + let backslashes = false; + + let output = input.replace(REGEX_SPECIAL_CHARS_BACKREF, (m, esc, chars, first, rest, index) => { + if (first === '\\') { + backslashes = true; + return m; + } + + if (first === '?') { + if (esc) { + return esc + first + (rest ? QMARK.repeat(rest.length) : ''); + } + if (index === 0) { + return qmarkNoDot + (rest ? QMARK.repeat(rest.length) : ''); + } + return QMARK.repeat(chars.length); + } + + if (first === '.') { + return DOT_LITERAL.repeat(chars.length); + } + + if (first === '*') { + if (esc) { + return esc + first + (rest ? star : ''); + } + return star; + } + return esc ? m : '\\' + m; + }); + + if (backslashes === true) { + if (opts.unescape === true) { + output = output.replace(/\\/g, ''); + } else { + output = output.replace(/\\+/g, m => { + return m.length % 2 === 0 ? '\\\\' : (m ? '\\' : ''); + }); + } + } + + state.output = output; + return state; + } + + /** + * Tokenize input until we reach end-of-string + */ + + while (!eos()) { + value = advance(); + + if (value === '\u0000') { + continue; + } + + /** + * Escaped characters + */ + + if (value === '\\') { + let next = peek(); + + if (next === '/' && opts.bash !== true) { + continue; + } + + if (next === '.' || next === ';') { + continue; + } + + if (!next) { + value += '\\'; + push({ type: 'text', value }); + continue; + } + + // collapse slashes to reduce potential for exploits + let match = /^\\+/.exec(input.slice(state.index + 1)); + let slashes = 0; + + if (match && match[0].length > 2) { + slashes = match[0].length; + state.index += slashes; + if (slashes % 2 !== 0) { + value += '\\'; + } + } + + if (opts.unescape === true) { + value = advance() || ''; + } else { + value += advance() || ''; + } + + if (state.brackets === 0) { + push({ type: 'text', value }); + continue; + } + } + + /** + * If we're inside a regex character class, continue + * until we reach the closing bracket. + */ + + if (state.brackets > 0 && (value !== ']' || prev.value === '[' || prev.value === '[^')) { + if (opts.posix !== false && value === ':') { + let inner = prev.value.slice(1); + if (inner.includes('[')) { + prev.posix = true; + + if (inner.includes(':')) { + let idx = prev.value.lastIndexOf('['); + let pre = prev.value.slice(0, idx); + let rest = prev.value.slice(idx + 2); + let posix = POSIX_REGEX_SOURCE[rest]; + if (posix) { + prev.value = pre + posix; + state.backtrack = true; + advance(); + + if (!bos.output && tokens.indexOf(prev) === 1) { + bos.output = ONE_CHAR; + } + continue; + } + } + } + } + + if ((value === '[' && peek() !== ':') || (value === '-' && peek() === ']')) { + value = '\\' + value; + } + + if (value === ']' && (prev.value === '[' || prev.value === '[^')) { + value = '\\' + value; + } + + if (opts.posix === true && value === '!' && prev.value === '[') { + value = '^'; + } + + prev.value += value; + append({ value }); + continue; + } + + /** + * If we're inside a quoted string, continue + * until we reach the closing double quote. + */ + + if (state.quotes === 1 && value !== '"') { + value = utils.escapeRegex(value); + prev.value += value; + append({ value }); + continue; + } + + /** + * Double quotes + */ + + if (value === '"') { + state.quotes = state.quotes === 1 ? 0 : 1; + if (opts.keepQuotes === true) { + push({ type: 'text', value }); + } + continue; + } + + /** + * Parentheses + */ + + if (value === '(') { + push({ type: 'paren', value }); + increment('parens'); + continue; + } + + if (value === ')') { + if (state.parens === 0 && opts.strictBrackets === true) { + throw new SyntaxError(syntaxError('opening', '(')); + } + + let extglob = extglobs[extglobs.length - 1]; + if (extglob && state.parens === extglob.parens + 1) { + extglobClose(extglobs.pop()); + continue; + } + + push({ type: 'paren', value, output: state.parens ? ')' : '\\)' }); + decrement('parens'); + continue; + } + + /** + * Brackets + */ + + if (value === '[') { + if (opts.nobracket === true || !input.slice(state.index + 1).includes(']')) { + if (opts.nobracket !== true && opts.strictBrackets === true) { + throw new SyntaxError(syntaxError('closing', ']')); + } + + value = '\\' + value; + } else { + increment('brackets'); + } + + push({ type: 'bracket', value }); + continue; + } + + if (value === ']') { + if (opts.nobracket === true || (prev && prev.type === 'bracket' && prev.value.length === 1)) { + push({ type: 'text', value, output: '\\' + value }); + continue; + } + + if (state.brackets === 0) { + if (opts.strictBrackets === true) { + throw new SyntaxError(syntaxError('opening', '[')); + } + + push({ type: 'text', value, output: '\\' + value }); + continue; + } + + decrement('brackets'); + + let prevValue = prev.value.slice(1); + if (prev.posix !== true && prevValue[0] === '^' && !prevValue.includes('/')) { + value = '/' + value; + } + + prev.value += value; + append({ value }); + + // when literal brackets are explicitly disabled + // assume we should match with a regex character class + if (opts.literalBrackets === false || utils.hasRegexChars(prevValue)) { + continue; + } + + let escaped = utils.escapeRegex(prev.value); + state.output = state.output.slice(0, -prev.value.length); + + // when literal brackets are explicitly enabled + // assume we should escape the brackets to match literal characters + if (opts.literalBrackets === true) { + state.output += escaped; + prev.value = escaped; + continue; + } + + // when the user specifies nothing, try to match both + prev.value = `(${capture}${escaped}|${prev.value})`; + state.output += prev.value; + continue; + } + + /** + * Braces + */ + + if (value === '{' && opts.nobrace !== true) { + push({ type: 'brace', value, output: '(' }); + increment('braces'); + continue; + } + + if (value === '}') { + if (opts.nobrace === true || state.braces === 0) { + push({ type: 'text', value, output: '\\' + value }); + continue; + } + + let output = ')'; + + if (state.dots === true) { + let arr = tokens.slice(); + let range = []; + + for (let i = arr.length - 1; i >= 0; i--) { + tokens.pop(); + if (arr[i].type === 'brace') { + break; + } + if (arr[i].type !== 'dots') { + range.unshift(arr[i].value); + } + } + + output = expandRange(range, opts); + state.backtrack = true; + } + + push({ type: 'brace', value, output }); + decrement('braces'); + continue; + } + + /** + * Pipes + */ + + if (value === '|') { + if (extglobs.length > 0) { + extglobs[extglobs.length - 1].conditions++; + } + push({ type: 'text', value }); + continue; + } + + /** + * Commas + */ + + if (value === ',') { + let output = value; + + if (state.braces > 0 && stack[stack.length - 1] === 'braces') { + output = '|'; + } + + push({ type: 'comma', value, output }); + continue; + } + + /** + * Slashes + */ + + if (value === '/') { + // if the beginning of the glob is "./", advance the start + // to the current index, and don't add the "./" characters + // to the state. This greatly simplifies lookbehinds when + // checking for BOS characters like "!" and "." (not "./") + if (prev.type === 'dot' && state.index === 1) { + state.start = state.index + 1; + state.consumed = ''; + state.output = ''; + tokens.pop(); + prev = bos; // reset "prev" to the first token + continue; + } + + push({ type: 'slash', value, output: SLASH_LITERAL }); + continue; + } + + /** + * Dots + */ + + if (value === '.') { + if (state.braces > 0 && prev.type === 'dot') { + if (prev.value === '.') prev.output = DOT_LITERAL; + prev.type = 'dots'; + prev.output += value; + prev.value += value; + state.dots = true; + continue; + } + + push({ type: 'dot', value, output: DOT_LITERAL }); + continue; + } + + /** + * Question marks + */ + + if (value === '?') { + if (prev && prev.type === 'paren') { + let next = peek(); + let output = value; + + if (next === '<' && !utils.supportsLookbehinds()) { + throw new Error('Node.js v10 or higher is required for regex lookbehinds'); + } + + if (prev.value === '(' && !/[!=<:]/.test(next) || (next === '<' && !/[!=]/.test(peek(2)))) { + output = '\\' + value; + } + + push({ type: 'text', value, output }); + continue; + } + + if (opts.noextglob !== true && peek() === '(' && peek(2) !== '?') { + extglobOpen('qmark', value); + continue; + } + + if (opts.dot !== true && (prev.type === 'slash' || prev.type === 'bos')) { + push({ type: 'qmark', value, output: QMARK_NO_DOT }); + continue; + } + + push({ type: 'qmark', value, output: QMARK }); + continue; + } + + /** + * Exclamation + */ + + if (value === '!') { + if (opts.noextglob !== true && peek() === '(') { + if (peek(2) !== '?' || !/[!=<:]/.test(peek(3))) { + extglobOpen('negate', value); + continue; + } + } + + if (opts.nonegate !== true && state.index === 0) { + negate(state); + continue; + } + } + + /** + * Plus + */ + + if (value === '+') { + if (opts.noextglob !== true && peek() === '(' && peek(2) !== '?') { + extglobOpen('plus', value); + continue; + } + + if (prev && (prev.type === 'bracket' || prev.type === 'paren' || prev.type === 'brace')) { + let output = prev.extglob === true ? '\\' + value : value; + push({ type: 'plus', value, output }); + continue; + } + + // use regex behavior inside parens + if (state.parens > 0 && opts.regex !== false) { + push({ type: 'plus', value }); + continue; + } + + push({ type: 'plus', value: PLUS_LITERAL }); + continue; + } + + /** + * Plain text + */ + + if (value === '@') { + if (opts.noextglob !== true && peek() === '(' && peek(2) !== '?') { + push({ type: 'at', value, output: '' }); + continue; + } + + push({ type: 'text', value }); + continue; + } + + /** + * Plain text + */ + + if (value !== '*') { + if (value === '$' || value === '^') { + value = '\\' + value; + } + + let match = REGEX_NON_SPECIAL_CHAR.exec(input.slice(state.index + 1)); + if (match) { + value += match[0]; + state.index += match[0].length; + } + + push({ type: 'text', value }); + continue; + } + + /** + * Stars + */ + + if (prev && (prev.type === 'globstar' || prev.star === true)) { + prev.type = 'star'; + prev.star = true; + prev.value += value; + prev.output = star; + state.backtrack = true; + state.consumed += value; + continue; + } + + if (opts.noextglob !== true && peek() === '(' && peek(2) !== '?') { + extglobOpen('star', value); + continue; + } + + if (prev.type === 'star') { + if (opts.noglobstar === true) { + state.consumed += value; + continue; + } + + let prior = prev.prev; + let before = prior.prev; + let isStart = prior.type === 'slash' || prior.type === 'bos'; + let afterStar = before && (before.type === 'star' || before.type === 'globstar'); + + if (opts.bash === true && (!isStart || (!eos() && peek() !== '/'))) { + push({ type: 'star', value, output: '' }); + continue; + } + + let isBrace = state.braces > 0 && (prior.type === 'comma' || prior.type === 'brace'); + let isExtglob = extglobs.length && (prior.type === 'pipe' || prior.type === 'paren'); + if (!isStart && prior.type !== 'paren' && !isBrace && !isExtglob) { + push({ type: 'star', value, output: '' }); + continue; + } + + // strip consecutive `/**/` + while (input.slice(state.index + 1, state.index + 4) === '/**') { + let after = input[state.index + 4]; + if (after && after !== '/') { + break; + } + state.consumed += '/**'; + state.index += 3; + } + + if (prior.type === 'bos' && eos()) { + prev.type = 'globstar'; + prev.value += value; + prev.output = globstar(opts); + state.output = prev.output; + state.consumed += value; + continue; + } + + if (prior.type === 'slash' && prior.prev.type !== 'bos' && !afterStar && eos()) { + state.output = state.output.slice(0, -(prior.output + prev.output).length); + prior.output = '(?:' + prior.output; + + prev.type = 'globstar'; + prev.output = globstar(opts) + '|$)'; + prev.value += value; + + state.output += prior.output + prev.output; + state.consumed += value; + continue; + } + + let next = peek(); + if (prior.type === 'slash' && prior.prev.type !== 'bos' && next === '/') { + let end = peek(2) !== void 0 ? '|$' : ''; + + state.output = state.output.slice(0, -(prior.output + prev.output).length); + prior.output = '(?:' + prior.output; + + prev.type = 'globstar'; + prev.output = `${globstar(opts)}${SLASH_LITERAL}|${SLASH_LITERAL}${end})`; + prev.value += value; + + state.output += prior.output + prev.output; + state.consumed += value + advance(); + + push({ type: 'slash', value, output: '' }); + continue; + } + + if (prior.type === 'bos' && next === '/') { + prev.type = 'globstar'; + prev.value += value; + prev.output = `(?:^|${SLASH_LITERAL}|${globstar(opts)}${SLASH_LITERAL})`; + state.output = prev.output; + state.consumed += value + advance(); + push({ type: 'slash', value, output: '' }); + continue; + } + + // remove single star from output + state.output = state.output.slice(0, -prev.output.length); + + // reset previous token to globstar + prev.type = 'globstar'; + prev.output = globstar(opts); + prev.value += value; + + // reset output with globstar + state.output += prev.output; + state.consumed += value; + continue; + } + + let token = { type: 'star', value, output: star }; + + if (opts.bash === true) { + token.output = '.*?'; + if (prev.type === 'bos' || prev.type === 'slash') { + token.output = nodot + token.output; + } + push(token); + continue; + } + + if (prev && (prev.type === 'bracket' || prev.type === 'paren') && opts.regex === true) { + token.output = value; + push(token); + continue; + } + + if (state.index === state.start || prev.type === 'slash' || prev.type === 'dot') { + if (prev.type === 'dot') { + state.output += NO_DOT_SLASH; + prev.output += NO_DOT_SLASH; + + } else if (opts.dot === true) { + state.output += NO_DOTS_SLASH; + prev.output += NO_DOTS_SLASH; + + } else { + state.output += nodot; + prev.output += nodot; + } + + if (peek() !== '*') { + state.output += ONE_CHAR; + prev.output += ONE_CHAR; + } + } + + push(token); + } + + while (state.brackets > 0) { + if (opts.strictBrackets === true) throw new SyntaxError(syntaxError('closing', ']')); + state.output = utils.escapeLast(state.output, '['); + decrement('brackets'); + } + + while (state.parens > 0) { + if (opts.strictBrackets === true) throw new SyntaxError(syntaxError('closing', ')')); + state.output = utils.escapeLast(state.output, '('); + decrement('parens'); + } + + while (state.braces > 0) { + if (opts.strictBrackets === true) throw new SyntaxError(syntaxError('closing', '}')); + state.output = utils.escapeLast(state.output, '{'); + decrement('braces'); + } + + if (opts.strictSlashes !== true && (prev.type === 'star' || prev.type === 'bracket')) { + push({ type: 'maybe_slash', value: '', output: `${SLASH_LITERAL}?` }); + } + + // rebuild the output if we had to backtrack at any point + if (state.backtrack === true) { + state.output = ''; + + for (let token of state.tokens) { + state.output += token.output != null ? token.output : token.value; + + if (token.suffix) { + state.output += token.suffix; + } + } + } + + return state; +}; + +/** + * Fast paths for creating regular expressions for common glob patterns. + * This can significantly speed up processing and has very little downside + * impact when none of the fast paths match. + */ + +parse.fastpaths = (input, options) => { + let opts = { ...options }; + let max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH; + let len = input.length; + if (len > max) { + throw new SyntaxError(`Input length: ${len}, exceeds maximum allowed length: ${max}`); + } + + input = REPLACEMENTS[input] || input; + let win32 = utils.isWindows(options); + + // create constants based on platform, for windows or posix + const { + DOT_LITERAL, + SLASH_LITERAL, + ONE_CHAR, + DOTS_SLASH, + NO_DOT, + NO_DOTS, + NO_DOTS_SLASH, + STAR, + START_ANCHOR + } = constants.globChars(win32); + + let capture = opts.capture ? '' : '?:'; + let star = opts.bash === true ? '.*?' : STAR; + let nodot = opts.dot ? NO_DOTS : NO_DOT; + let slashDot = opts.dot ? NO_DOTS_SLASH : NO_DOT; + + if (opts.capture) { + star = `(${star})`; + } + + const globstar = (opts) => { + return `(${capture}(?:(?!${START_ANCHOR}${opts.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`; + }; + + const create = str => { + switch (str) { + case '*': + return `${nodot}${ONE_CHAR}${star}`; + + case '.*': + return `${DOT_LITERAL}${ONE_CHAR}${star}`; + + case '*.*': + return `${nodot}${star}${DOT_LITERAL}${ONE_CHAR}${star}`; + + case '*/*': + return `${nodot}${star}${SLASH_LITERAL}${ONE_CHAR}${slashDot}${star}`; + + case '**': + return nodot + globstar(opts); + + case '**/*': + return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${slashDot}${ONE_CHAR}${star}`; + + case '**/*.*': + return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${slashDot}${star}${DOT_LITERAL}${ONE_CHAR}${star}`; + + case '**/.*': + return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${DOT_LITERAL}${ONE_CHAR}${star}`; + + default: { + let match = /^(.*?)\.(\w+)$/.exec(str); + if (!match) return; + + let source = create(match[1], options); + if (!source) return; + + return source + DOT_LITERAL + match[2]; + } + } + }; + + let output = create(input); + if (output && opts.strictSlashes !== true) { + output += `${SLASH_LITERAL}?`; + } + + return output; +}; + +module.exports = parse; + + +/***/ }), +/* 206 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const merge2 = __webpack_require__(177); +function merge(streams) { + const mergedStream = merge2(streams); + streams.forEach((stream) => { + stream.once('error', (err) => mergedStream.emit('error', err)); + }); + return mergedStream; +} +exports.merge = merge; + + +/***/ }), +/* 207 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const stream_1 = __webpack_require__(208); +const provider_1 = __webpack_require__(235); +class ProviderAsync extends provider_1.default { + constructor() { + super(...arguments); + this._reader = new stream_1.default(this._settings); + } + read(task) { + const root = this._getRootDirectory(task); + const options = this._getReaderOptions(task); + const entries = []; + return new Promise((resolve, reject) => { + const stream = this.api(root, task, options); + stream.once('error', reject); + stream.on('data', (entry) => entries.push(options.transform(entry))); + stream.once('end', () => resolve(entries)); + }); + } + api(root, task, options) { + if (task.dynamic) { + return this._reader.dynamic(root, options); + } + return this._reader.static(task.patterns, options); + } +} +exports.default = ProviderAsync; + + +/***/ }), +/* 208 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const stream_1 = __webpack_require__(28); +const fsStat = __webpack_require__(209); +const fsWalk = __webpack_require__(214); +const reader_1 = __webpack_require__(234); +class ReaderStream extends reader_1.default { + constructor() { + super(...arguments); + this._walkStream = fsWalk.walkStream; + this._stat = fsStat.stat; + } + dynamic(root, options) { + return this._walkStream(root, options); + } + static(patterns, options) { + const filepaths = patterns.map(this._getFullEntryPath, this); + const stream = new stream_1.PassThrough({ objectMode: true }); + stream._write = (index, _enc, done) => { + return this._getEntry(filepaths[index], patterns[index], options) + .then((entry) => { + if (entry !== null && options.entryFilter(entry)) { + stream.push(entry); + } + if (index === filepaths.length - 1) { + stream.end(); + } + done(); + }) + .catch(done); + }; + for (let i = 0; i < filepaths.length; i++) { + stream.write(i); + } + return stream; + } + _getEntry(filepath, pattern, options) { + return this._getStat(filepath) + .then((stats) => this._makeEntry(stats, pattern)) + .catch((error) => { + if (options.errorFilter(error)) { + return null; + } + throw error; + }); + } + _getStat(filepath) { + return new Promise((resolve, reject) => { + this._stat(filepath, this._fsStatSettings, (error, stats) => { + error ? reject(error) : resolve(stats); + }); + }); + } +} +exports.default = ReaderStream; + + +/***/ }), +/* 209 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const async = __webpack_require__(210); +const sync = __webpack_require__(211); +const settings_1 = __webpack_require__(212); +exports.Settings = settings_1.default; +function stat(path, optionsOrSettingsOrCallback, callback) { + if (typeof optionsOrSettingsOrCallback === 'function') { + return async.read(path, getSettings(), optionsOrSettingsOrCallback); + } + async.read(path, getSettings(optionsOrSettingsOrCallback), callback); +} +exports.stat = stat; +function statSync(path, optionsOrSettings) { + const settings = getSettings(optionsOrSettings); + return sync.read(path, settings); +} +exports.statSync = statSync; +function getSettings(settingsOrOptions = {}) { + if (settingsOrOptions instanceof settings_1.default) { + return settingsOrOptions; + } + return new settings_1.default(settingsOrOptions); +} + + +/***/ }), +/* 210 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +function read(path, settings, callback) { + settings.fs.lstat(path, (lstatError, lstat) => { + if (lstatError) { + return callFailureCallback(callback, lstatError); + } + if (!lstat.isSymbolicLink() || !settings.followSymbolicLink) { + return callSuccessCallback(callback, lstat); + } + settings.fs.stat(path, (statError, stat) => { + if (statError) { + if (settings.throwErrorOnBrokenSymbolicLink) { + return callFailureCallback(callback, statError); + } + return callSuccessCallback(callback, lstat); + } + if (settings.markSymbolicLink) { + stat.isSymbolicLink = () => true; + } + callSuccessCallback(callback, stat); + }); + }); +} +exports.read = read; +function callFailureCallback(callback, error) { + callback(error); +} +function callSuccessCallback(callback, result) { + callback(null, result); +} + + +/***/ }), +/* 211 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +function read(path, settings) { + const lstat = settings.fs.lstatSync(path); + if (!lstat.isSymbolicLink() || !settings.followSymbolicLink) { + return lstat; + } + try { + const stat = settings.fs.statSync(path); + if (settings.markSymbolicLink) { + stat.isSymbolicLink = () => true; + } + return stat; + } + catch (error) { + if (!settings.throwErrorOnBrokenSymbolicLink) { + return lstat; + } + throw error; + } +} +exports.read = read; + + +/***/ }), +/* 212 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const fs = __webpack_require__(213); +class Settings { + constructor(_options = {}) { + this._options = _options; + this.followSymbolicLink = this._getValue(this._options.followSymbolicLink, true); + this.fs = fs.createFileSystemAdapter(this._options.fs); + this.markSymbolicLink = this._getValue(this._options.markSymbolicLink, false); + this.throwErrorOnBrokenSymbolicLink = this._getValue(this._options.throwErrorOnBrokenSymbolicLink, true); + } + _getValue(option, value) { + return option === undefined ? value : option; + } +} +exports.default = Settings; + + +/***/ }), +/* 213 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const fs = __webpack_require__(23); +exports.FILE_SYSTEM_ADAPTER = { + lstat: fs.lstat, + stat: fs.stat, + lstatSync: fs.lstatSync, + statSync: fs.statSync +}; +function createFileSystemAdapter(fsMethods) { + if (!fsMethods) { + return exports.FILE_SYSTEM_ADAPTER; + } + return Object.assign({}, exports.FILE_SYSTEM_ADAPTER, fsMethods); +} +exports.createFileSystemAdapter = createFileSystemAdapter; + + +/***/ }), +/* 214 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const async_1 = __webpack_require__(215); +const stream_1 = __webpack_require__(230); +const sync_1 = __webpack_require__(231); +const settings_1 = __webpack_require__(233); +exports.Settings = settings_1.default; +function walk(dir, optionsOrSettingsOrCallback, callback) { + if (typeof optionsOrSettingsOrCallback === 'function') { + return new async_1.default(dir, getSettings()).read(optionsOrSettingsOrCallback); + } + new async_1.default(dir, getSettings(optionsOrSettingsOrCallback)).read(callback); +} +exports.walk = walk; +function walkSync(dir, optionsOrSettings) { + const settings = getSettings(optionsOrSettings); + const provider = new sync_1.default(dir, settings); + return provider.read(); +} +exports.walkSync = walkSync; +function walkStream(dir, optionsOrSettings) { + const settings = getSettings(optionsOrSettings); + const provider = new stream_1.default(dir, settings); + return provider.read(); +} +exports.walkStream = walkStream; +function getSettings(settingsOrOptions = {}) { + if (settingsOrOptions instanceof settings_1.default) { + return settingsOrOptions; + } + return new settings_1.default(settingsOrOptions); +} + + +/***/ }), +/* 215 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const async_1 = __webpack_require__(216); +class AsyncProvider { + constructor(_root, _settings) { + this._root = _root; + this._settings = _settings; + this._reader = new async_1.default(this._root, this._settings); + this._storage = new Set(); + } + read(callback) { + this._reader.onError((error) => { + callFailureCallback(callback, error); + }); + this._reader.onEntry((entry) => { + this._storage.add(entry); + }); + this._reader.onEnd(() => { + callSuccessCallback(callback, Array.from(this._storage)); + }); + this._reader.read(); + } +} +exports.default = AsyncProvider; +function callFailureCallback(callback, error) { + callback(error); +} +function callSuccessCallback(callback, entries) { + callback(null, entries); +} + + +/***/ }), +/* 216 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const events_1 = __webpack_require__(46); +const fsScandir = __webpack_require__(217); +const fastq = __webpack_require__(226); +const common = __webpack_require__(228); +const reader_1 = __webpack_require__(229); +class AsyncReader extends reader_1.default { + constructor(_root, _settings) { + super(_root, _settings); + this._settings = _settings; + this._scandir = fsScandir.scandir; + this._emitter = new events_1.EventEmitter(); + this._queue = fastq(this._worker.bind(this), this._settings.concurrency); + this._isFatalError = false; + this._isDestroyed = false; + this._queue.drain = () => { + if (!this._isFatalError) { + this._emitter.emit('end'); + } + }; + } + read() { + this._isFatalError = false; + this._isDestroyed = false; + setImmediate(() => { + this._pushToQueue(this._root, this._settings.basePath); + }); + return this._emitter; + } + destroy() { + if (this._isDestroyed) { + throw new Error('The reader is already destroyed'); + } + this._isDestroyed = true; + this._queue.killAndDrain(); + } + onEntry(callback) { + this._emitter.on('entry', callback); + } + onError(callback) { + this._emitter.once('error', callback); + } + onEnd(callback) { + this._emitter.once('end', callback); + } + _pushToQueue(dir, base) { + const queueItem = { dir, base }; + this._queue.push(queueItem, (error) => { + if (error) { + this._handleError(error); + } + }); + } + _worker(item, done) { + this._scandir(item.dir, this._settings.fsScandirSettings, (error, entries) => { + if (error) { + return done(error, undefined); + } + for (const entry of entries) { + this._handleEntry(entry, item.base); + } + done(null, undefined); + }); + } + _handleError(error) { + if (!common.isFatalError(this._settings, error)) { + return; + } + this._isFatalError = true; + this._isDestroyed = true; + this._emitter.emit('error', error); + } + _handleEntry(entry, base) { + if (this._isDestroyed || this._isFatalError) { + return; + } + const fullpath = entry.path; + if (base !== undefined) { + entry.path = common.joinPathSegments(base, entry.name, this._settings.pathSegmentSeparator); + } + if (common.isAppliedFilter(this._settings.entryFilter, entry)) { + this._emitEntry(entry); + } + if (entry.dirent.isDirectory() && common.isAppliedFilter(this._settings.deepFilter, entry)) { + this._pushToQueue(fullpath, entry.path); + } + } + _emitEntry(entry) { + this._emitter.emit('entry', entry); + } +} +exports.default = AsyncReader; + + +/***/ }), +/* 217 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const async = __webpack_require__(218); +const sync = __webpack_require__(223); +const settings_1 = __webpack_require__(224); +exports.Settings = settings_1.default; +function scandir(path, optionsOrSettingsOrCallback, callback) { + if (typeof optionsOrSettingsOrCallback === 'function') { + return async.read(path, getSettings(), optionsOrSettingsOrCallback); + } + async.read(path, getSettings(optionsOrSettingsOrCallback), callback); +} +exports.scandir = scandir; +function scandirSync(path, optionsOrSettings) { + const settings = getSettings(optionsOrSettings); + return sync.read(path, settings); +} +exports.scandirSync = scandirSync; +function getSettings(settingsOrOptions = {}) { + if (settingsOrOptions instanceof settings_1.default) { + return settingsOrOptions; + } + return new settings_1.default(settingsOrOptions); +} + + +/***/ }), +/* 218 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const fsStat = __webpack_require__(209); +const rpl = __webpack_require__(219); +const constants_1 = __webpack_require__(220); +const utils = __webpack_require__(221); +function read(dir, settings, callback) { + if (!settings.stats && constants_1.IS_SUPPORT_READDIR_WITH_FILE_TYPES) { + return readdirWithFileTypes(dir, settings, callback); + } + return readdir(dir, settings, callback); +} +exports.read = read; +function readdirWithFileTypes(dir, settings, callback) { + settings.fs.readdir(dir, { withFileTypes: true }, (readdirError, dirents) => { + if (readdirError) { + return callFailureCallback(callback, readdirError); + } + const entries = dirents.map((dirent) => ({ + dirent, + name: dirent.name, + path: `${dir}${settings.pathSegmentSeparator}${dirent.name}` + })); + if (!settings.followSymbolicLinks) { + return callSuccessCallback(callback, entries); + } + const tasks = entries.map((entry) => makeRplTaskEntry(entry, settings)); + rpl(tasks, (rplError, rplEntries) => { + if (rplError) { + return callFailureCallback(callback, rplError); + } + callSuccessCallback(callback, rplEntries); + }); + }); +} +exports.readdirWithFileTypes = readdirWithFileTypes; +function makeRplTaskEntry(entry, settings) { + return (done) => { + if (!entry.dirent.isSymbolicLink()) { + return done(null, entry); + } + settings.fs.stat(entry.path, (statError, stats) => { + if (statError) { + if (settings.throwErrorOnBrokenSymbolicLink) { + return done(statError); + } + return done(null, entry); + } + entry.dirent = utils.fs.createDirentFromStats(entry.name, stats); + return done(null, entry); + }); + }; +} +function readdir(dir, settings, callback) { + settings.fs.readdir(dir, (readdirError, names) => { + if (readdirError) { + return callFailureCallback(callback, readdirError); + } + const filepaths = names.map((name) => `${dir}${settings.pathSegmentSeparator}${name}`); + const tasks = filepaths.map((filepath) => { + return (done) => fsStat.stat(filepath, settings.fsStatSettings, done); + }); + rpl(tasks, (rplError, results) => { + if (rplError) { + return callFailureCallback(callback, rplError); + } + const entries = []; + for (let index = 0; index < names.length; index++) { + const name = names[index]; + const stats = results[index]; + const entry = { + name, + path: filepaths[index], + dirent: utils.fs.createDirentFromStats(name, stats) + }; + if (settings.stats) { + entry.stats = stats; + } + entries.push(entry); + } + callSuccessCallback(callback, entries); + }); + }); +} +exports.readdir = readdir; +function callFailureCallback(callback, error) { + callback(error); +} +function callSuccessCallback(callback, result) { + callback(null, result); +} + + +/***/ }), +/* 219 */ +/***/ (function(module, exports) { + +module.exports = runParallel + +function runParallel (tasks, cb) { + var results, pending, keys + var isSync = true + + if (Array.isArray(tasks)) { + results = [] + pending = tasks.length + } else { + keys = Object.keys(tasks) + results = {} + pending = keys.length + } + + function done (err) { + function end () { + if (cb) cb(err, results) + cb = null + } + if (isSync) process.nextTick(end) + else end() + } + + function each (i, err, result) { + results[i] = result + if (--pending === 0 || err) { + done(err) + } + } + + if (!pending) { + // empty + done(null) + } else if (keys) { + // object + keys.forEach(function (key) { + tasks[key](function (err, result) { each(key, err, result) }) + }) + } else { + // array + tasks.forEach(function (task, i) { + task(function (err, result) { each(i, err, result) }) + }) + } + + isSync = false +} + + +/***/ }), +/* 220 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const NODE_PROCESS_VERSION_PARTS = process.versions.node.split('.'); +const MAJOR_VERSION = parseInt(NODE_PROCESS_VERSION_PARTS[0], 10); +const MINOR_VERSION = parseInt(NODE_PROCESS_VERSION_PARTS[1], 10); +/** + * IS `true` for Node.js 10.10 and greater. + */ +exports.IS_SUPPORT_READDIR_WITH_FILE_TYPES = MAJOR_VERSION > 10 || (MAJOR_VERSION === 10 && MINOR_VERSION >= 10); + + +/***/ }), +/* 221 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const fs = __webpack_require__(222); +exports.fs = fs; + + +/***/ }), +/* 222 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +class DirentFromStats { + constructor(name, stats) { + this.name = name; + this.isBlockDevice = stats.isBlockDevice.bind(stats); + this.isCharacterDevice = stats.isCharacterDevice.bind(stats); + this.isDirectory = stats.isDirectory.bind(stats); + this.isFIFO = stats.isFIFO.bind(stats); + this.isFile = stats.isFile.bind(stats); + this.isSocket = stats.isSocket.bind(stats); + this.isSymbolicLink = stats.isSymbolicLink.bind(stats); + } +} +function createDirentFromStats(name, stats) { + return new DirentFromStats(name, stats); +} +exports.createDirentFromStats = createDirentFromStats; + + +/***/ }), +/* 223 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const fsStat = __webpack_require__(209); +const constants_1 = __webpack_require__(220); +const utils = __webpack_require__(221); +function read(dir, settings) { + if (!settings.stats && constants_1.IS_SUPPORT_READDIR_WITH_FILE_TYPES) { + return readdirWithFileTypes(dir, settings); + } + return readdir(dir, settings); +} +exports.read = read; +function readdirWithFileTypes(dir, settings) { + const dirents = settings.fs.readdirSync(dir, { withFileTypes: true }); + return dirents.map((dirent) => { + const entry = { + dirent, + name: dirent.name, + path: `${dir}${settings.pathSegmentSeparator}${dirent.name}` + }; + if (entry.dirent.isSymbolicLink() && settings.followSymbolicLinks) { + try { + const stats = settings.fs.statSync(entry.path); + entry.dirent = utils.fs.createDirentFromStats(entry.name, stats); + } + catch (error) { + if (settings.throwErrorOnBrokenSymbolicLink) { + throw error; + } + } + } + return entry; + }); +} +exports.readdirWithFileTypes = readdirWithFileTypes; +function readdir(dir, settings) { + const names = settings.fs.readdirSync(dir); + return names.map((name) => { + const entryPath = `${dir}${settings.pathSegmentSeparator}${name}`; + const stats = fsStat.statSync(entryPath, settings.fsStatSettings); + const entry = { + name, + path: entryPath, + dirent: utils.fs.createDirentFromStats(name, stats) + }; + if (settings.stats) { + entry.stats = stats; + } + return entry; + }); +} +exports.readdir = readdir; + + +/***/ }), +/* 224 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const path = __webpack_require__(16); +const fsStat = __webpack_require__(209); +const fs = __webpack_require__(225); +class Settings { + constructor(_options = {}) { + this._options = _options; + this.followSymbolicLinks = this._getValue(this._options.followSymbolicLinks, false); + this.fs = fs.createFileSystemAdapter(this._options.fs); + this.pathSegmentSeparator = this._getValue(this._options.pathSegmentSeparator, path.sep); + this.stats = this._getValue(this._options.stats, false); + this.throwErrorOnBrokenSymbolicLink = this._getValue(this._options.throwErrorOnBrokenSymbolicLink, true); + this.fsStatSettings = new fsStat.Settings({ + followSymbolicLink: this.followSymbolicLinks, + fs: this.fs, + throwErrorOnBrokenSymbolicLink: this.throwErrorOnBrokenSymbolicLink + }); + } + _getValue(option, value) { + return option === undefined ? value : option; + } +} +exports.default = Settings; + + +/***/ }), +/* 225 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const fs = __webpack_require__(23); +exports.FILE_SYSTEM_ADAPTER = { + lstat: fs.lstat, + stat: fs.stat, + lstatSync: fs.lstatSync, + statSync: fs.statSync, + readdir: fs.readdir, + readdirSync: fs.readdirSync +}; +function createFileSystemAdapter(fsMethods) { + if (!fsMethods) { + return exports.FILE_SYSTEM_ADAPTER; + } + return Object.assign({}, exports.FILE_SYSTEM_ADAPTER, fsMethods); +} +exports.createFileSystemAdapter = createFileSystemAdapter; + + +/***/ }), +/* 226 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var reusify = __webpack_require__(227) + +function fastqueue (context, worker, concurrency) { + if (typeof context === 'function') { + concurrency = worker + worker = context + context = null + } + + var cache = reusify(Task) + var queueHead = null + var queueTail = null + var _running = 0 + + var self = { + push: push, + drain: noop, + saturated: noop, + pause: pause, + paused: false, + concurrency: concurrency, + running: running, + resume: resume, + idle: idle, + length: length, + unshift: unshift, + empty: noop, + kill: kill, + killAndDrain: killAndDrain + } + + return self + + function running () { + return _running + } + + function pause () { + self.paused = true + } + + function length () { + var current = queueHead + var counter = 0 + + while (current) { + current = current.next + counter++ + } + + return counter + } + + function resume () { + if (!self.paused) return + self.paused = false + for (var i = 0; i < self.concurrency; i++) { + _running++ + release() + } + } + + function idle () { + return _running === 0 && self.length() === 0 + } + + function push (value, done) { + var current = cache.get() + + current.context = context + current.release = release + current.value = value + current.callback = done || noop + + if (_running === self.concurrency || self.paused) { + if (queueTail) { + queueTail.next = current + queueTail = current + } else { + queueHead = current + queueTail = current + self.saturated() + } + } else { + _running++ + worker.call(context, current.value, current.worked) + } + } + + function unshift (value, done) { + var current = cache.get() + + current.context = context + current.release = release + current.value = value + current.callback = done || noop + + if (_running === self.concurrency || self.paused) { + if (queueHead) { + current.next = queueHead + queueHead = current + } else { + queueHead = current + queueTail = current + self.saturated() + } + } else { + _running++ + worker.call(context, current.value, current.worked) + } + } + + function release (holder) { + if (holder) { + cache.release(holder) + } + var next = queueHead + if (next) { + if (!self.paused) { + if (queueTail === queueHead) { + queueTail = null + } + queueHead = next.next + next.next = null + worker.call(context, next.value, next.worked) + if (queueTail === null) { + self.empty() + } + } else { + _running-- + } + } else if (--_running === 0) { + self.drain() + } + } + + function kill () { + queueHead = null + queueTail = null + self.drain = noop + } + + function killAndDrain () { + queueHead = null + queueTail = null + self.drain() + self.drain = noop + } +} + +function noop () {} + +function Task () { + this.value = null + this.callback = noop + this.next = null + this.release = noop + this.context = null + + var self = this + + this.worked = function worked (err, result) { + var callback = self.callback + self.value = null + self.callback = noop + callback.call(self.context, err, result) + self.release(self) + } +} + +module.exports = fastqueue + + +/***/ }), +/* 227 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +function reusify (Constructor) { + var head = new Constructor() + var tail = head + + function get () { + var current = head + + if (current.next) { + head = current.next + } else { + head = new Constructor() + tail = head + } + + current.next = null + + return current + } + + function release (obj) { + tail.next = obj + tail = obj + } + + return { + get: get, + release: release + } +} + +module.exports = reusify + + +/***/ }), +/* 228 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +function isFatalError(settings, error) { + if (settings.errorFilter === null) { + return true; + } + return !settings.errorFilter(error); +} +exports.isFatalError = isFatalError; +function isAppliedFilter(filter, value) { + return filter === null || filter(value); +} +exports.isAppliedFilter = isAppliedFilter; +function replacePathSegmentSeparator(filepath, separator) { + return filepath.split(/[\\\/]/).join(separator); +} +exports.replacePathSegmentSeparator = replacePathSegmentSeparator; +function joinPathSegments(a, b, separator) { + if (a === '') { + return b; + } + return a + separator + b; +} +exports.joinPathSegments = joinPathSegments; + + +/***/ }), +/* 229 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const common = __webpack_require__(228); +class Reader { + constructor(_root, _settings) { + this._root = _root; + this._settings = _settings; + this._root = common.replacePathSegmentSeparator(_root, _settings.pathSegmentSeparator); + } +} +exports.default = Reader; + + +/***/ }), +/* 230 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const stream_1 = __webpack_require__(28); +const async_1 = __webpack_require__(216); +class StreamProvider { + constructor(_root, _settings) { + this._root = _root; + this._settings = _settings; + this._reader = new async_1.default(this._root, this._settings); + this._stream = new stream_1.Readable({ + objectMode: true, + read: () => { }, + destroy: this._reader.destroy.bind(this._reader) + }); + } + read() { + this._reader.onError((error) => { + this._stream.emit('error', error); + }); + this._reader.onEntry((entry) => { + this._stream.push(entry); + }); + this._reader.onEnd(() => { + this._stream.push(null); + }); + this._reader.read(); + return this._stream; + } +} +exports.default = StreamProvider; + + +/***/ }), +/* 231 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const sync_1 = __webpack_require__(232); +class SyncProvider { + constructor(_root, _settings) { + this._root = _root; + this._settings = _settings; + this._reader = new sync_1.default(this._root, this._settings); + } + read() { + return this._reader.read(); + } +} +exports.default = SyncProvider; + + +/***/ }), +/* 232 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const fsScandir = __webpack_require__(217); +const common = __webpack_require__(228); +const reader_1 = __webpack_require__(229); +class SyncReader extends reader_1.default { + constructor() { + super(...arguments); + this._scandir = fsScandir.scandirSync; + this._storage = new Set(); + this._queue = new Set(); + } + read() { + this._pushToQueue(this._root, this._settings.basePath); + this._handleQueue(); + return Array.from(this._storage); + } + _pushToQueue(dir, base) { + this._queue.add({ dir, base }); + } + _handleQueue() { + for (const item of this._queue.values()) { + this._handleDirectory(item.dir, item.base); + } + } + _handleDirectory(dir, base) { + try { + const entries = this._scandir(dir, this._settings.fsScandirSettings); + for (const entry of entries) { + this._handleEntry(entry, base); + } + } + catch (error) { + this._handleError(error); + } + } + _handleError(error) { + if (!common.isFatalError(this._settings, error)) { + return; + } + throw error; + } + _handleEntry(entry, base) { + const fullpath = entry.path; + if (base !== undefined) { + entry.path = common.joinPathSegments(base, entry.name, this._settings.pathSegmentSeparator); + } + if (common.isAppliedFilter(this._settings.entryFilter, entry)) { + this._pushToStorage(entry); + } + if (entry.dirent.isDirectory() && common.isAppliedFilter(this._settings.deepFilter, entry)) { + this._pushToQueue(fullpath, entry.path); + } + } + _pushToStorage(entry) { + this._storage.add(entry); + } +} +exports.default = SyncReader; + + +/***/ }), +/* 233 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const path = __webpack_require__(16); +const fsScandir = __webpack_require__(217); +class Settings { + constructor(_options = {}) { + this._options = _options; + this.basePath = this._getValue(this._options.basePath, undefined); + this.concurrency = this._getValue(this._options.concurrency, Infinity); + this.deepFilter = this._getValue(this._options.deepFilter, null); + this.entryFilter = this._getValue(this._options.entryFilter, null); + this.errorFilter = this._getValue(this._options.errorFilter, null); + this.pathSegmentSeparator = this._getValue(this._options.pathSegmentSeparator, path.sep); + this.fsScandirSettings = new fsScandir.Settings({ + followSymbolicLinks: this._options.followSymbolicLinks, + fs: this._options.fs, + pathSegmentSeparator: this._options.pathSegmentSeparator, + stats: this._options.stats, + throwErrorOnBrokenSymbolicLink: this._options.throwErrorOnBrokenSymbolicLink + }); + } + _getValue(option, value) { + return option === undefined ? value : option; + } +} +exports.default = Settings; + + +/***/ }), +/* 234 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const path = __webpack_require__(16); +const fsStat = __webpack_require__(209); +const utils = __webpack_require__(180); +class Reader { + constructor(_settings) { + this._settings = _settings; + this._fsStatSettings = new fsStat.Settings({ + followSymbolicLink: this._settings.followSymbolicLinks, + fs: this._settings.fs, + throwErrorOnBrokenSymbolicLink: this._settings.followSymbolicLinks + }); + } + _getFullEntryPath(filepath) { + return path.resolve(this._settings.cwd, filepath); + } + _makeEntry(stats, pattern) { + const entry = { + name: pattern, + path: pattern, + dirent: utils.fs.createDirentFromStats(pattern, stats) + }; + if (this._settings.stats) { + entry.stats = stats; + } + return entry; + } + _isFatalError(error) { + return !utils.errno.isEnoentCodeError(error) && !this._settings.suppressErrors; + } +} +exports.default = Reader; + + +/***/ }), +/* 235 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const path = __webpack_require__(16); +const deep_1 = __webpack_require__(236); +const entry_1 = __webpack_require__(237); +const error_1 = __webpack_require__(238); +const entry_2 = __webpack_require__(239); +class Provider { + constructor(_settings) { + this._settings = _settings; + this.errorFilter = new error_1.default(this._settings); + this.entryFilter = new entry_1.default(this._settings, this._getMicromatchOptions()); + this.deepFilter = new deep_1.default(this._settings, this._getMicromatchOptions()); + this.entryTransformer = new entry_2.default(this._settings); + } + _getRootDirectory(task) { + return path.resolve(this._settings.cwd, task.base); + } + _getReaderOptions(task) { + const basePath = task.base === '.' ? '' : task.base; + return { + basePath, + pathSegmentSeparator: '/', + concurrency: this._settings.concurrency, + deepFilter: this.deepFilter.getFilter(basePath, task.positive, task.negative), + entryFilter: this.entryFilter.getFilter(task.positive, task.negative), + errorFilter: this.errorFilter.getFilter(), + followSymbolicLinks: this._settings.followSymbolicLinks, + fs: this._settings.fs, + stats: this._settings.stats, + throwErrorOnBrokenSymbolicLink: this._settings.throwErrorOnBrokenSymbolicLink, + transform: this.entryTransformer.getTransformer() + }; + } + _getMicromatchOptions() { + return { + dot: this._settings.dot, + matchBase: this._settings.baseNameMatch, + nobrace: !this._settings.braceExpansion, + nocase: !this._settings.caseSensitiveMatch, + noext: !this._settings.extglob, + noglobstar: !this._settings.globstar, + posix: true, + strictSlashes: false + }; + } +} +exports.default = Provider; + + +/***/ }), +/* 236 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const utils = __webpack_require__(180); +class DeepFilter { + constructor(_settings, _micromatchOptions) { + this._settings = _settings; + this._micromatchOptions = _micromatchOptions; + } + getFilter(basePath, positive, negative) { + const maxPatternDepth = this._getMaxPatternDepth(positive); + const negativeRe = this._getNegativePatternsRe(negative); + return (entry) => this._filter(basePath, entry, negativeRe, maxPatternDepth); + } + _getMaxPatternDepth(patterns) { + const globstar = patterns.some(utils.pattern.hasGlobStar); + return globstar ? Infinity : utils.pattern.getMaxNaivePatternsDepth(patterns); + } + _getNegativePatternsRe(patterns) { + const affectDepthOfReadingPatterns = patterns.filter(utils.pattern.isAffectDepthOfReadingPattern); + return utils.pattern.convertPatternsToRe(affectDepthOfReadingPatterns, this._micromatchOptions); + } + _filter(basePath, entry, negativeRe, maxPatternDepth) { + const depth = this._getEntryDepth(basePath, entry.path); + if (this._isSkippedByDeep(depth)) { + return false; + } + if (this._isSkippedByMaxPatternDepth(depth, maxPatternDepth)) { + return false; + } + if (this._isSkippedSymbolicLink(entry)) { + return false; + } + if (this._isSkippedDotDirectory(entry)) { + return false; + } + return this._isSkippedByNegativePatterns(entry, negativeRe); + } + _getEntryDepth(basePath, entryPath) { + const basePathDepth = basePath.split('/').length; + const entryPathDepth = entryPath.split('/').length; + return entryPathDepth - (basePath === '' ? 0 : basePathDepth); + } + _isSkippedByDeep(entryDepth) { + return entryDepth >= this._settings.deep; + } + _isSkippedByMaxPatternDepth(entryDepth, maxPatternDepth) { + return !this._settings.baseNameMatch && maxPatternDepth !== Infinity && entryDepth > maxPatternDepth; + } + _isSkippedSymbolicLink(entry) { + return !this._settings.followSymbolicLinks && entry.dirent.isSymbolicLink(); + } + _isSkippedDotDirectory(entry) { + return !this._settings.dot && entry.name.startsWith('.'); + } + _isSkippedByNegativePatterns(entry, negativeRe) { + return !utils.pattern.matchAny(entry.path, negativeRe); + } +} +exports.default = DeepFilter; + + +/***/ }), +/* 237 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const utils = __webpack_require__(180); +class EntryFilter { + constructor(_settings, _micromatchOptions) { + this._settings = _settings; + this._micromatchOptions = _micromatchOptions; + this.index = new Map(); + } + getFilter(positive, negative) { + const positiveRe = utils.pattern.convertPatternsToRe(positive, this._micromatchOptions); + const negativeRe = utils.pattern.convertPatternsToRe(negative, this._micromatchOptions); + return (entry) => this._filter(entry, positiveRe, negativeRe); + } + _filter(entry, positiveRe, negativeRe) { + if (this._settings.unique) { + if (this._isDuplicateEntry(entry)) { + return false; + } + this._createIndexRecord(entry); + } + if (this._onlyFileFilter(entry) || this._onlyDirectoryFilter(entry)) { + return false; + } + if (this._isSkippedByAbsoluteNegativePatterns(entry, negativeRe)) { + return false; + } + const filepath = this._settings.baseNameMatch ? entry.name : entry.path; + return this._isMatchToPatterns(filepath, positiveRe) && !this._isMatchToPatterns(entry.path, negativeRe); + } + _isDuplicateEntry(entry) { + return this.index.has(entry.path); + } + _createIndexRecord(entry) { + this.index.set(entry.path, undefined); + } + _onlyFileFilter(entry) { + return this._settings.onlyFiles && !entry.dirent.isFile(); + } + _onlyDirectoryFilter(entry) { + return this._settings.onlyDirectories && !entry.dirent.isDirectory(); + } + _isSkippedByAbsoluteNegativePatterns(entry, negativeRe) { + if (!this._settings.absolute) { + return false; + } + const fullpath = utils.path.makeAbsolute(this._settings.cwd, entry.path); + return this._isMatchToPatterns(fullpath, negativeRe); + } + _isMatchToPatterns(filepath, patternsRe) { + return utils.pattern.matchAny(filepath, patternsRe); + } +} +exports.default = EntryFilter; + + +/***/ }), +/* 238 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const utils = __webpack_require__(180); +class ErrorFilter { + constructor(_settings) { + this._settings = _settings; + } + getFilter() { + return (error) => this._isNonFatalError(error); + } + _isNonFatalError(error) { + return utils.errno.isEnoentCodeError(error) || this._settings.suppressErrors; + } +} +exports.default = ErrorFilter; + + +/***/ }), +/* 239 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const utils = __webpack_require__(180); +class EntryTransformer { + constructor(_settings) { + this._settings = _settings; + } + getTransformer() { + return (entry) => this._transform(entry); + } + _transform(entry) { + let filepath = entry.path; + if (this._settings.absolute) { + filepath = utils.path.makeAbsolute(this._settings.cwd, filepath); + filepath = utils.path.unixify(filepath); + } + if (this._settings.markDirectories && entry.dirent.isDirectory()) { + filepath += '/'; + } + if (!this._settings.objectMode) { + return filepath; + } + return Object.assign({}, entry, { path: filepath }); + } +} +exports.default = EntryTransformer; + + +/***/ }), +/* 240 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const stream_1 = __webpack_require__(28); +const stream_2 = __webpack_require__(208); +const provider_1 = __webpack_require__(235); +class ProviderStream extends provider_1.default { + constructor() { + super(...arguments); + this._reader = new stream_2.default(this._settings); + } + read(task) { + const root = this._getRootDirectory(task); + const options = this._getReaderOptions(task); + const source = this.api(root, task, options); + const dest = new stream_1.Readable({ objectMode: true, read: () => { } }); + source + .once('error', (error) => dest.emit('error', error)) + .on('data', (entry) => dest.emit('data', options.transform(entry))) + .once('end', () => dest.emit('end')); + return dest; + } + api(root, task, options) { + if (task.dynamic) { + return this._reader.dynamic(root, options); + } + return this._reader.static(task.patterns, options); + } +} +exports.default = ProviderStream; + + +/***/ }), +/* 241 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const sync_1 = __webpack_require__(242); +const provider_1 = __webpack_require__(235); +class ProviderSync extends provider_1.default { + constructor() { + super(...arguments); + this._reader = new sync_1.default(this._settings); + } + read(task) { + const root = this._getRootDirectory(task); + const options = this._getReaderOptions(task); + const entries = this.api(root, task, options); + return entries.map(options.transform); + } + api(root, task, options) { + if (task.dynamic) { + return this._reader.dynamic(root, options); + } + return this._reader.static(task.patterns, options); + } +} +exports.default = ProviderSync; + + +/***/ }), +/* 242 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const fsStat = __webpack_require__(209); +const fsWalk = __webpack_require__(214); +const reader_1 = __webpack_require__(234); +class ReaderSync extends reader_1.default { + constructor() { + super(...arguments); + this._walkSync = fsWalk.walkSync; + this._statSync = fsStat.statSync; + } + dynamic(root, options) { + return this._walkSync(root, options); + } + static(patterns, options) { + const entries = []; + for (const pattern of patterns) { + const filepath = this._getFullEntryPath(pattern); + const entry = this._getEntry(filepath, pattern, options); + if (entry === null || !options.entryFilter(entry)) { + continue; + } + entries.push(entry); + } + return entries; + } + _getEntry(filepath, pattern, options) { + try { + const stats = this._getStat(filepath); + return this._makeEntry(stats, pattern); + } + catch (error) { + if (options.errorFilter(error)) { + return null; + } + throw error; + } + } + _getStat(filepath) { + return this._statSync(filepath, this._fsStatSettings); + } +} +exports.default = ReaderSync; + + +/***/ }), +/* 243 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const fs = __webpack_require__(23); +const os = __webpack_require__(11); +const CPU_COUNT = os.cpus().length; +exports.DEFAULT_FILE_SYSTEM_ADAPTER = { + lstat: fs.lstat, + lstatSync: fs.lstatSync, + stat: fs.stat, + statSync: fs.statSync, + readdir: fs.readdir, + readdirSync: fs.readdirSync +}; +// tslint:enable no-redundant-jsdoc +class Settings { + constructor(_options = {}) { + this._options = _options; + this.absolute = this._getValue(this._options.absolute, false); + this.baseNameMatch = this._getValue(this._options.baseNameMatch, false); + this.braceExpansion = this._getValue(this._options.braceExpansion, true); + this.caseSensitiveMatch = this._getValue(this._options.caseSensitiveMatch, true); + this.concurrency = this._getValue(this._options.concurrency, CPU_COUNT); + this.cwd = this._getValue(this._options.cwd, process.cwd()); + this.deep = this._getValue(this._options.deep, Infinity); + this.dot = this._getValue(this._options.dot, false); + this.extglob = this._getValue(this._options.extglob, true); + this.followSymbolicLinks = this._getValue(this._options.followSymbolicLinks, true); + this.fs = this._getFileSystemMethods(this._options.fs); + this.globstar = this._getValue(this._options.globstar, true); + this.ignore = this._getValue(this._options.ignore, []); + this.markDirectories = this._getValue(this._options.markDirectories, false); + this.objectMode = this._getValue(this._options.objectMode, false); + this.onlyDirectories = this._getValue(this._options.onlyDirectories, false); + this.onlyFiles = this._getValue(this._options.onlyFiles, true); + this.stats = this._getValue(this._options.stats, false); + this.suppressErrors = this._getValue(this._options.suppressErrors, false); + this.throwErrorOnBrokenSymbolicLink = this._getValue(this._options.throwErrorOnBrokenSymbolicLink, false); + this.unique = this._getValue(this._options.unique, true); + if (this.onlyDirectories) { + this.onlyFiles = false; + } + if (this.stats) { + this.objectMode = true; + } + } + _getValue(option, value) { + return option === undefined ? value : option; + } + _getFileSystemMethods(methods = {}) { + return Object.assign({}, exports.DEFAULT_FILE_SYSTEM_ADAPTER, methods); + } +} +exports.default = Settings; + + +/***/ }), +/* 244 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const path = __webpack_require__(16); +const pathType = __webpack_require__(245); + +const getExtensions = extensions => extensions.length > 1 ? `{${extensions.join(',')}}` : extensions[0]; + +const getPath = (filepath, cwd) => { + const pth = filepath[0] === '!' ? filepath.slice(1) : filepath; + return path.isAbsolute(pth) ? pth : path.join(cwd, pth); +}; + +const addExtensions = (file, extensions) => { + if (path.extname(file)) { + return `**/${file}`; + } + + return `**/${file}.${getExtensions(extensions)}`; +}; + +const getGlob = (directory, options) => { + if (options.files && !Array.isArray(options.files)) { + throw new TypeError(`Expected \`files\` to be of type \`Array\` but received type \`${typeof options.files}\``); + } + + if (options.extensions && !Array.isArray(options.extensions)) { + throw new TypeError(`Expected \`extensions\` to be of type \`Array\` but received type \`${typeof options.extensions}\``); + } + + if (options.files && options.extensions) { + return options.files.map(x => path.posix.join(directory, addExtensions(x, options.extensions))); + } + + if (options.files) { + return options.files.map(x => path.posix.join(directory, `**/${x}`)); + } + + if (options.extensions) { + return [path.posix.join(directory, `**/*.${getExtensions(options.extensions)}`)]; + } + + return [path.posix.join(directory, '**')]; +}; + +module.exports = async (input, options) => { + options = { + cwd: process.cwd(), + ...options + }; + + if (typeof options.cwd !== 'string') { + throw new TypeError(`Expected \`cwd\` to be of type \`string\` but received type \`${typeof options.cwd}\``); + } + + const globs = await Promise.all([].concat(input).map(async x => { + const isDirectory = await pathType.isDirectory(getPath(x, options.cwd)); + return isDirectory ? getGlob(x, options) : x; + })); + + return [].concat.apply([], globs); // eslint-disable-line prefer-spread +}; + +module.exports.sync = (input, options) => { + options = { + cwd: process.cwd(), + ...options + }; + + if (typeof options.cwd !== 'string') { + throw new TypeError(`Expected \`cwd\` to be of type \`string\` but received type \`${typeof options.cwd}\``); + } + + const globs = [].concat(input).map(x => pathType.isDirectorySync(getPath(x, options.cwd)) ? getGlob(x, options) : x); + + return [].concat.apply([], globs); // eslint-disable-line prefer-spread +}; + + +/***/ }), +/* 245 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const {promisify} = __webpack_require__(29); +const fs = __webpack_require__(23); + +async function isType(fsStatType, statsMethodName, filePath) { + if (typeof filePath !== 'string') { + throw new TypeError(`Expected a string, got ${typeof filePath}`); + } + + try { + const stats = await promisify(fs[fsStatType])(filePath); + return stats[statsMethodName](); + } catch (error) { + if (error.code === 'ENOENT') { + return false; + } + + throw error; + } +} + +function isTypeSync(fsStatType, statsMethodName, filePath) { + if (typeof filePath !== 'string') { + throw new TypeError(`Expected a string, got ${typeof filePath}`); + } + + try { + return fs[fsStatType](filePath)[statsMethodName](); + } catch (error) { + if (error.code === 'ENOENT') { + return false; + } + + throw error; + } +} + +exports.isFile = isType.bind(null, 'stat', 'isFile'); +exports.isDirectory = isType.bind(null, 'stat', 'isDirectory'); +exports.isSymlink = isType.bind(null, 'lstat', 'isSymbolicLink'); +exports.isFileSync = isTypeSync.bind(null, 'statSync', 'isFile'); +exports.isDirectorySync = isTypeSync.bind(null, 'statSync', 'isDirectory'); +exports.isSymlinkSync = isTypeSync.bind(null, 'lstatSync', 'isSymbolicLink'); + + +/***/ }), +/* 246 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const {promisify} = __webpack_require__(29); +const fs = __webpack_require__(23); +const path = __webpack_require__(16); +const fastGlob = __webpack_require__(178); +const gitIgnore = __webpack_require__(247); +const slash = __webpack_require__(248); + +const DEFAULT_IGNORE = [ + '**/node_modules/**', + '**/flow-typed/**', + '**/coverage/**', + '**/.git' +]; + +const readFileP = promisify(fs.readFile); + +const mapGitIgnorePatternTo = base => ignore => { + if (ignore.startsWith('!')) { + return '!' + path.posix.join(base, ignore.slice(1)); + } + + return path.posix.join(base, ignore); +}; + +const parseGitIgnore = (content, options) => { + const base = slash(path.relative(options.cwd, path.dirname(options.fileName))); + + return content + .split(/\r?\n/) + .filter(Boolean) + .filter(line => !line.startsWith('#')) + .map(mapGitIgnorePatternTo(base)); +}; + +const reduceIgnore = files => { + return files.reduce((ignores, file) => { + ignores.add(parseGitIgnore(file.content, { + cwd: file.cwd, + fileName: file.filePath + })); + return ignores; + }, gitIgnore()); +}; + +const ensureAbsolutePathForCwd = (cwd, p) => { + if (path.isAbsolute(p)) { + if (p.startsWith(cwd)) { + return p; + } + + throw new Error(`Path ${p} is not in cwd ${cwd}`); + } + + return path.join(cwd, p); +}; + +const getIsIgnoredPredecate = (ignores, cwd) => { + return p => ignores.ignores(slash(path.relative(cwd, ensureAbsolutePathForCwd(cwd, p)))); +}; + +const getFile = async (file, cwd) => { + const filePath = path.join(cwd, file); + const content = await readFileP(filePath, 'utf8'); + + return { + cwd, + filePath, + content + }; +}; + +const getFileSync = (file, cwd) => { + const filePath = path.join(cwd, file); + const content = fs.readFileSync(filePath, 'utf8'); + + return { + cwd, + filePath, + content + }; +}; + +const normalizeOptions = ({ + ignore = [], + cwd = process.cwd() +} = {}) => { + return {ignore, cwd}; +}; + +module.exports = async options => { + options = normalizeOptions(options); + + const paths = await fastGlob('**/.gitignore', { + ignore: DEFAULT_IGNORE.concat(options.ignore), + cwd: options.cwd + }); + + const files = await Promise.all(paths.map(file => getFile(file, options.cwd))); + const ignores = reduceIgnore(files); + + return getIsIgnoredPredecate(ignores, options.cwd); +}; + +module.exports.sync = options => { + options = normalizeOptions(options); + + const paths = fastGlob.sync('**/.gitignore', { + ignore: DEFAULT_IGNORE.concat(options.ignore), + cwd: options.cwd + }); + + const files = paths.map(file => getFileSync(file, options.cwd)); + const ignores = reduceIgnore(files); + + return getIsIgnoredPredecate(ignores, options.cwd); +}; + + +/***/ }), +/* 247 */ +/***/ (function(module, exports) { + +// A simple implementation of make-array +function makeArray (subject) { + return Array.isArray(subject) + ? subject + : [subject] +} + +const REGEX_TEST_BLANK_LINE = /^\s+$/ +const REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION = /^\\!/ +const REGEX_REPLACE_LEADING_EXCAPED_HASH = /^\\#/ +const REGEX_SPLITALL_CRLF = /\r?\n/g +// /foo, +// ./foo, +// ../foo, +// . +// .. +const REGEX_TEST_INVALID_PATH = /^\.*\/|^\.+$/ + +const SLASH = '/' +const KEY_IGNORE = typeof Symbol !== 'undefined' + ? Symbol.for('node-ignore') + /* istanbul ignore next */ + : 'node-ignore' + +const define = (object, key, value) => + Object.defineProperty(object, key, {value}) + +const REGEX_REGEXP_RANGE = /([0-z])-([0-z])/g + +// Sanitize the range of a regular expression +// The cases are complicated, see test cases for details +const sanitizeRange = range => range.replace( + REGEX_REGEXP_RANGE, + (match, from, to) => from.charCodeAt(0) <= to.charCodeAt(0) + ? match + // Invalid range (out of order) which is ok for gitignore rules but + // fatal for JavaScript regular expression, so eliminate it. + : '' +) + +// > If the pattern ends with a slash, +// > it is removed for the purpose of the following description, +// > but it would only find a match with a directory. +// > In other words, foo/ will match a directory foo and paths underneath it, +// > but will not match a regular file or a symbolic link foo +// > (this is consistent with the way how pathspec works in general in Git). +// '`foo/`' will not match regular file '`foo`' or symbolic link '`foo`' +// -> ignore-rules will not deal with it, because it costs extra `fs.stat` call +// you could use option `mark: true` with `glob` + +// '`foo/`' should not continue with the '`..`' +const DEFAULT_REPLACER_PREFIX = [ + + // > Trailing spaces are ignored unless they are quoted with backslash ("\") + [ + // (a\ ) -> (a ) + // (a ) -> (a) + // (a \ ) -> (a ) + /\\?\s+$/, + match => match.indexOf('\\') === 0 + ? ' ' + : '' + ], + + // replace (\ ) with ' ' + [ + /\\\s/g, + () => ' ' + ], + + // Escape metacharacters + // which is written down by users but means special for regular expressions. + + // > There are 12 characters with special meanings: + // > - the backslash \, + // > - the caret ^, + // > - the dollar sign $, + // > - the period or dot ., + // > - the vertical bar or pipe symbol |, + // > - the question mark ?, + // > - the asterisk or star *, + // > - the plus sign +, + // > - the opening parenthesis (, + // > - the closing parenthesis ), + // > - and the opening square bracket [, + // > - the opening curly brace {, + // > These special characters are often called "metacharacters". + [ + /[\\^$.|*+(){]/g, + match => `\\${match}` + ], + + [ + // > [abc] matches any character inside the brackets + // > (in this case a, b, or c); + /\[([^\]/]*)($|\])/g, + (match, p1, p2) => p2 === ']' + ? `[${sanitizeRange(p1)}]` + : `\\${match}` + ], -function isNumber (x) { - if (typeof x === 'number') return true; - if (/^0x[0-9a-f]+$/i.test(x)) return true; - return /^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(e[-+]?\d+)?$/.test(x); -} + [ + // > a question mark (?) matches a single character + /(?!\\)\?/g, + () => '[^/]' + ], + // leading slash + [ + // > A leading slash matches the beginning of the pathname. + // > For example, "/*.c" matches "cat-file.c" but not "mozilla-sha1/sha1.c". + // A leading slash matches the beginning of the pathname + /^\//, + () => '^' + ], -/***/ }), -/* 162 */ -/***/ (function(module) { + // replace special metacharacter slash after the leading slash + [ + /\//g, + () => '\\/' + ], -module.exports = JSON.parse("{\"name\":\"strong-log-transformer\",\"version\":\"2.1.0\",\"description\":\"Stream transformer that prefixes lines with timestamps and other things.\",\"author\":\"Ryan Graham \",\"license\":\"Apache-2.0\",\"repository\":{\"type\":\"git\",\"url\":\"git://github.com/strongloop/strong-log-transformer\"},\"keywords\":[\"logging\",\"streams\"],\"bugs\":{\"url\":\"https://github.com/strongloop/strong-log-transformer/issues\"},\"homepage\":\"https://github.com/strongloop/strong-log-transformer\",\"directories\":{\"test\":\"test\"},\"bin\":{\"sl-log-transformer\":\"bin/sl-log-transformer.js\"},\"main\":\"index.js\",\"scripts\":{\"test\":\"tap --100 test/test-*\"},\"dependencies\":{\"duplexer\":\"^0.1.1\",\"minimist\":\"^1.2.0\",\"through\":\"^2.3.4\"},\"devDependencies\":{\"tap\":\"^12.0.1\"},\"engines\":{\"node\":\">=4\"}}"); + [ + // > A leading "**" followed by a slash means match in all directories. + // > For example, "**/foo" matches file or directory "foo" anywhere, + // > the same as pattern "foo". + // > "**/foo/bar" matches file or directory "bar" anywhere that is directly + // > under directory "foo". + // Notice that the '*'s have been replaced as '\\*' + /^\^*\\\*\\\*\\\//, -/***/ }), -/* 163 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + // '**/foo' <-> 'foo' + () => '^(?:.*\\/)?' + ] +] -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "workspacePackagePaths", function() { return workspacePackagePaths; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "copyWorkspacePackages", function() { return copyWorkspacePackages; }); -/* harmony import */ var glob__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(37); -/* harmony import */ var glob__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(glob__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(16); -/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_1__); -/* harmony import */ var util__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(29); -/* harmony import */ var util__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(util__WEBPACK_IMPORTED_MODULE_2__); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(164); -/* harmony import */ var _fs__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(20); -/* harmony import */ var _package_json__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(55); -/* harmony import */ var _projects__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(36); -/* - * 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 DEFAULT_REPLACER_SUFFIX = [ + // starting + [ + // there will be no leading '/' + // (which has been replaced by section "leading slash") + // If starts with '**', adding a '^' to the regular expression also works + /^(?=[^^])/, + function startingReplacer () { + return !/\/(?!$)/.test(this) + // > If the pattern does not contain a slash /, + // > Git treats it as a shell glob pattern + // Actually, if there is only a trailing slash, + // git also treats it as a shell glob pattern + ? '(?:^|\\/)' + // > Otherwise, Git treats the pattern as a shell glob suitable for + // > consumption by fnmatch(3) + : '^' + } + ], + // two globstars + [ + // Use lookahead assertions so that we could match more than one `'/**'` + /\\\/\\\*\\\*(?=\\\/|$)/g, + // Zero, one or several directories + // should not use '*', or it will be replaced by the next replacer + // Check if it is not the last `'/**'` + (_, index, str) => index + 6 < str.length + // case: /**/ + // > A slash followed by two consecutive asterisks then a slash matches + // > zero or more directories. + // > For example, "a/**/b" matches "a/b", "a/x/b", "a/x/y/b" and so on. + // '/**/' + ? '(?:\\/[^\\/]+)*' + // case: /** + // > A trailing `"/**"` matches everything inside. -const glob = Object(util__WEBPACK_IMPORTED_MODULE_2__["promisify"])(glob__WEBPACK_IMPORTED_MODULE_0___default.a); -async function workspacePackagePaths(rootPath) { - const rootPkgJson = await Object(_package_json__WEBPACK_IMPORTED_MODULE_5__["readPackageJson"])(rootPath); + // #21: everything inside but it should not include the current folder + : '\\/.+' + ], - if (!rootPkgJson.workspaces) { - return []; - } + // intermediate wildcards + [ + // Never replace escaped '*' + // ignore rule '\*' will match the path '*' - const workspacesPathsPatterns = rootPkgJson.workspaces.packages; - let workspaceProjectsPaths = []; + // 'abc.*/' -> go + // 'abc.*' -> skip this rule + /(^|[^\\]+)\\\*(?=.+)/g, - for (const pattern of workspacesPathsPatterns) { - workspaceProjectsPaths = workspaceProjectsPaths.concat((await packagesFromGlobPattern({ - pattern, - rootPath - }))); - } // Filter out exclude glob patterns + // '*.js' matches '.js' + // '*.js' doesn't match 'abc' + (_, p1) => `${p1}[^\\/]*` + ], + // trailing wildcard + [ + /(\^|\\\/)?\\\*$/, + (_, p1) => { + const prefix = p1 + // '\^': + // '/*' does not match '' + // '/*' does not match everything - for (const pattern of workspacesPathsPatterns) { - if (pattern.startsWith('!')) { - const pathToRemove = path__WEBPACK_IMPORTED_MODULE_1___default.a.join(rootPath, pattern.slice(1), 'package.json'); - workspaceProjectsPaths = workspaceProjectsPaths.filter(p => p !== pathToRemove); - } - } + // '\\\/': + // 'abc/*' does not match 'abc/' + ? `${p1}[^/]+` - return workspaceProjectsPaths; -} -async function copyWorkspacePackages(rootPath) { - const projectPaths = Object(_config__WEBPACK_IMPORTED_MODULE_3__["getProjectPaths"])(rootPath, {}); - const projects = await Object(_projects__WEBPACK_IMPORTED_MODULE_6__["getProjects"])(rootPath, projectPaths); + // 'a*' matches 'a' + // 'a*' matches 'aa' + : '[^/]*' - for (const project of projects.values()) { - const dest = path__WEBPACK_IMPORTED_MODULE_1___default.a.resolve(rootPath, 'node_modules', project.name); + return `${prefix}(?=$|\\/$)` + } + ], - if ((await Object(_fs__WEBPACK_IMPORTED_MODULE_4__["isSymlink"])(dest)) === false) { - continue; - } // Remove the symlink + [ + // unescape + /\\\\\\/g, + () => '\\' + ] +] +const POSITIVE_REPLACERS = [ + ...DEFAULT_REPLACER_PREFIX, - await Object(_fs__WEBPACK_IMPORTED_MODULE_4__["unlink"])(dest); // Copy in the package + // 'f' + // matches + // - /f(end) + // - /f/ + // - (start)f(end) + // - (start)f/ + // doesn't match + // - oof + // - foo + // pseudo: + // -> (^|/)f(/|$) - await Object(_fs__WEBPACK_IMPORTED_MODULE_4__["copyDirectory"])(project.path, dest); - } -} + // ending + [ + // 'js' will not match 'js.' + // 'ab' will not match 'abc' + /(?:[^*/])$/, -function packagesFromGlobPattern({ - pattern, - rootPath -}) { - const globOptions = { - cwd: rootPath, - // Should throw in case of unusual errors when reading the file system - strict: true, - // Always returns absolute paths for matched files - absolute: true, - // Do not match ** against multiple filenames - // (This is only specified because we currently don't have a need for it.) - noglobstar: true - }; - return glob(path__WEBPACK_IMPORTED_MODULE_1___default.a.join(pattern, 'package.json'), globOptions); -} + // 'js*' will not match 'a.js' + // 'js/' will not match 'a.js' + // 'js' will match 'a.js' and 'a.js/' + match => `${match}(?=$|\\/)` + ], -/***/ }), -/* 164 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + ...DEFAULT_REPLACER_SUFFIX +] -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "getProjectPaths", function() { return getProjectPaths; }); -/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(16); -/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_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 NEGATIVE_REPLACERS = [ + ...DEFAULT_REPLACER_PREFIX, + // #24, #38 + // The MISSING rule of [gitignore docs](https://git-scm.com/docs/gitignore) + // A negative pattern without a trailing wildcard should not + // re-include the things inside that directory. -/** - * Returns all the paths where plugins are located - */ -function getProjectPaths(rootPath, options = {}) { - const skipKibanaPlugins = Boolean(options['skip-kibana-plugins']); - const ossOnly = Boolean(options.oss); - const projectPaths = [rootPath, Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'packages/*')]; // This is needed in order to install the dependencies for the declared - // plugin functional used in the selenium functional tests. - // As we are now using the webpack dll for the client vendors dependencies - // when we run the plugin functional tests against the distributable - // dependencies used by such plugins like @eui, react and react-dom can't - // be loaded from the dll as the context is different from the one declared - // into the webpack dll reference plugin. - // In anyway, have a plugin declaring their own dependencies is the - // correct and the expect behavior. + // eg: + // ['node_modules/*', '!node_modules'] + // should ignore `node_modules/a.js` + [ + /(?:[^*])$/, + match => `${match}(?=$|\\/$)` + ], - projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'test/plugin_functional/plugins/*')); - projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'test/interpreter_functional/plugins/*')); + ...DEFAULT_REPLACER_SUFFIX +] - if (!ossOnly) { - projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'x-pack')); - projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'x-pack/legacy/plugins/*')); - } +// A simple cache, because an ignore rule only has only one certain meaning +const regexCache = Object.create(null) - if (!skipKibanaPlugins) { - projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, '../kibana-extra/*')); - projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, '../kibana-extra/*/packages/*')); - projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, '../kibana-extra/*/plugins/*')); - projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'plugins/*')); - projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'plugins/*/packages/*')); - projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'plugins/*/plugins/*')); +// @param {pattern} +const makeRegex = (pattern, negative, ignorecase) => { + const r = regexCache[pattern] + if (r) { + return r } - return projectPaths; -} - -/***/ }), -/* 165 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + const replacers = negative + ? NEGATIVE_REPLACERS + : POSITIVE_REPLACERS -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CleanCommand", function() { return CleanCommand; }); -/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2); -/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(chalk__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(166); -/* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(del__WEBPACK_IMPORTED_MODULE_1__); -/* harmony import */ var ora__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(181); -/* harmony import */ var ora__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(ora__WEBPACK_IMPORTED_MODULE_2__); -/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(16); -/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_3__); -/* harmony import */ var _utils_fs__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(20); -/* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(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. - */ + const source = replacers.reduce( + (prev, current) => prev.replace(current[0], current[1].bind(pattern)), + pattern + ) + return regexCache[pattern] = ignorecase + ? new RegExp(source, 'i') + : new RegExp(source) +} +const isString = subject => typeof subject === 'string' +// > A blank line matches no files, so it can serve as a separator for readability. +const checkPattern = pattern => pattern + && isString(pattern) + && !REGEX_TEST_BLANK_LINE.test(pattern) + // > A line starting with # serves as a comment. + && pattern.indexOf('#') !== 0 +const splitPattern = pattern => pattern.split(REGEX_SPLITALL_CRLF) -const CleanCommand = { - description: 'Remove the node_modules and target directories from all projects.', - name: 'clean', +class IgnoreRule { + constructor ( + origin, + pattern, + negative, + regex + ) { + this.origin = origin + this.pattern = pattern + this.negative = negative + this.regex = regex + } +} - async run(projects) { - const toDelete = []; +const createRule = (pattern, ignorecase) => { + const origin = pattern + let negative = false - for (const project of projects.values()) { - if (await Object(_utils_fs__WEBPACK_IMPORTED_MODULE_4__["isDirectory"])(project.nodeModulesLocation)) { - toDelete.push({ - cwd: project.path, - pattern: Object(path__WEBPACK_IMPORTED_MODULE_3__["relative"])(project.path, project.nodeModulesLocation) - }); - } + // > An optional prefix "!" which negates the pattern; + if (pattern.indexOf('!') === 0) { + negative = true + pattern = pattern.substr(1) + } - if (await Object(_utils_fs__WEBPACK_IMPORTED_MODULE_4__["isDirectory"])(project.targetLocation)) { - toDelete.push({ - cwd: project.path, - pattern: Object(path__WEBPACK_IMPORTED_MODULE_3__["relative"])(project.path, project.targetLocation) - }); - } + pattern = pattern + // > Put a backslash ("\") in front of the first "!" for patterns that + // > begin with a literal "!", for example, `"\!important!.txt"`. + .replace(REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION, '!') + // > Put a backslash ("\") in front of the first hash for patterns that + // > begin with a hash. + .replace(REGEX_REPLACE_LEADING_EXCAPED_HASH, '#') - const { - extraPatterns - } = project.getCleanConfig(); + const regex = makeRegex(pattern, negative, ignorecase) - if (extraPatterns) { - toDelete.push({ - cwd: project.path, - pattern: extraPatterns - }); - } - } + return new IgnoreRule( + origin, + pattern, + negative, + regex + ) +} - if (toDelete.length === 0) { - _utils_log__WEBPACK_IMPORTED_MODULE_5__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.bold.green('\n\nNothing to delete')); - } else { - _utils_log__WEBPACK_IMPORTED_MODULE_5__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.bold.red('\n\nDeleting:\n')); - /** - * In order to avoid patterns like `/build` in packages from accidentally - * impacting files outside the package we use `process.chdir()` to change - * the cwd to the package and execute `del()` without the `force` option - * so it will check that each file being deleted is within the package. - * - * `del()` does support a `cwd` option, but it's only for resolving the - * patterns and does not impact the cwd check. - */ +const throwError = (message, Ctor) => { + throw new Ctor(message) +} - const originalCwd = process.cwd(); +const checkPath = (path, originalPath, doThrow) => { + if (!isString(path)) { + return doThrow( + `path must be a string, but got \`${originalPath}\``, + TypeError + ) + } - try { - for (const _ref of toDelete) { - const { - pattern, - cwd - } = _ref; - process.chdir(cwd); - const promise = del__WEBPACK_IMPORTED_MODULE_1___default()(pattern); - ora__WEBPACK_IMPORTED_MODULE_2___default.a.promise(promise, Object(path__WEBPACK_IMPORTED_MODULE_3__["relative"])(originalCwd, Object(path__WEBPACK_IMPORTED_MODULE_3__["join"])(cwd, String(pattern)))); - await promise; - } - } finally { - process.chdir(originalCwd); - } - } + // We don't know if we should ignore '', so throw + if (!path) { + return doThrow(`path must not be empty`, TypeError) } -}; + // Check if it is a relative path + if (checkPath.isNotRelative(path)) { + const r = '`path.relative()`d' + return doThrow( + `path should be a ${r} string, but got "${originalPath}"`, + RangeError + ) + } -/***/ }), -/* 166 */ -/***/ (function(module, exports, __webpack_require__) { + return true +} -"use strict"; +const isNotRelative = path => REGEX_TEST_INVALID_PATH.test(path) -const path = __webpack_require__(16); -const globby = __webpack_require__(167); -const isPathCwd = __webpack_require__(174); -const isPathInCwd = __webpack_require__(175); -const pify = __webpack_require__(178); -const rimraf = __webpack_require__(179); -const pMap = __webpack_require__(180); +checkPath.isNotRelative = isNotRelative +checkPath.convert = p => p -const rimrafP = pify(rimraf); +class Ignore { + constructor ({ + ignorecase = true + } = {}) { + this._rules = [] + this._ignorecase = ignorecase + define(this, KEY_IGNORE, true) + this._initCache() + } -function safeCheck(file) { - if (isPathCwd(file)) { - throw new Error('Cannot delete the current working directory. Can be overridden with the `force` option.'); - } + _initCache () { + this._ignoreCache = Object.create(null) + this._testCache = Object.create(null) + } - if (!isPathInCwd(file)) { - throw new Error('Cannot delete files/folders outside the current working directory. Can be overridden with the `force` option.'); - } -} + _addPattern (pattern) { + // #32 + if (pattern && pattern[KEY_IGNORE]) { + this._rules = this._rules.concat(pattern._rules) + this._added = true + return + } -const del = (patterns, options) => { - options = Object.assign({}, options); + if (checkPattern(pattern)) { + const rule = createRule(pattern, this._ignorecase) + this._added = true + this._rules.push(rule) + } + } - const {force, dryRun} = options; - delete options.force; - delete options.dryRun; + // @param {Array | string | Ignore} pattern + add (pattern) { + this._added = false - const mapper = file => { - if (!force) { - safeCheck(file); - } + makeArray( + isString(pattern) + ? splitPattern(pattern) + : pattern + ).forEach(this._addPattern, this) - file = path.resolve(options.cwd || '', file); + // Some rules have just added to the ignore, + // making the behavior changed. + if (this._added) { + this._initCache() + } - if (dryRun) { - return file; - } + return this + } - return rimrafP(file, {glob: false}).then(() => file); - }; + // legacy + addPattern (pattern) { + return this.add(pattern) + } - return globby(patterns, options).then(files => pMap(files, mapper, options)); -}; + // | ignored : unignored + // negative | 0:0 | 0:1 | 1:0 | 1:1 + // -------- | ------- | ------- | ------- | -------- + // 0 | TEST | TEST | SKIP | X + // 1 | TESTIF | SKIP | TEST | X -module.exports = del; -// TODO: Remove this for the next major release -module.exports.default = del; + // - SKIP: always skip + // - TEST: always test + // - TESTIF: only test if checkUnignored + // - X: that never happen -module.exports.sync = (patterns, options) => { - options = Object.assign({}, options); + // @param {boolean} whether should check if the path is unignored, + // setting `checkUnignored` to `false` could reduce additional + // path matching. - const {force, dryRun} = options; - delete options.force; - delete options.dryRun; + // @returns {TestResult} true if a file is ignored + _testOne (path, checkUnignored) { + let ignored = false + let unignored = false - return globby.sync(patterns, options).map(file => { - if (!force) { - safeCheck(file); - } + this._rules.forEach(rule => { + const {negative} = rule + if ( + unignored === negative && ignored !== unignored + || negative && !ignored && !unignored && !checkUnignored + ) { + return + } - file = path.resolve(options.cwd || '', file); + const matched = rule.regex.test(path) - if (!dryRun) { - rimraf.sync(file, {glob: false}); - } + if (matched) { + ignored = !negative + unignored = negative + } + }) - return file; - }); -}; + return { + ignored, + unignored + } + } + // @returns {TestResult} + _test (originalPath, cache, checkUnignored, slices) { + const path = originalPath + // Supports nullable path + && checkPath.convert(originalPath) -/***/ }), -/* 167 */ -/***/ (function(module, exports, __webpack_require__) { + checkPath(path, originalPath, throwError) -"use strict"; + return this._t(path, cache, checkUnignored, slices) + } -var Promise = __webpack_require__(168); -var arrayUnion = __webpack_require__(170); -var objectAssign = __webpack_require__(172); -var glob = __webpack_require__(37); -var pify = __webpack_require__(173); + _t (path, cache, checkUnignored, slices) { + if (path in cache) { + return cache[path] + } -var globP = pify(glob, Promise).bind(glob); + if (!slices) { + // path/to/a.js + // ['path', 'to', 'a.js'] + slices = path.split(SLASH) + } -function isNegative(pattern) { - return pattern[0] === '!'; -} + slices.pop() -function isString(value) { - return typeof value === 'string'; -} + // If the path has no parent directory, just test it + if (!slices.length) { + return cache[path] = this._testOne(path, checkUnignored) + } -function assertPatternsInput(patterns) { - if (!patterns.every(isString)) { - throw new TypeError('patterns must be a string or an array of strings'); - } -} + const parent = this._t( + slices.join(SLASH) + SLASH, + cache, + checkUnignored, + slices + ) -function generateGlobTasks(patterns, opts) { - patterns = [].concat(patterns); - assertPatternsInput(patterns); + // If the path contains a parent directory, check the parent first + return cache[path] = parent.ignored + // > It is not possible to re-include a file if a parent directory of + // > that file is excluded. + ? parent + : this._testOne(path, checkUnignored) + } - var globTasks = []; + ignores (path) { + return this._test(path, this._ignoreCache, false).ignored + } - opts = objectAssign({ - cache: Object.create(null), - statCache: Object.create(null), - realpathCache: Object.create(null), - symlinks: Object.create(null), - ignore: [] - }, opts); + createFilter () { + return path => !this.ignores(path) + } - patterns.forEach(function (pattern, i) { - if (isNegative(pattern)) { - return; - } + filter (paths) { + return makeArray(paths).filter(this.createFilter()) + } - var ignore = patterns.slice(i).filter(isNegative).map(function (pattern) { - return pattern.slice(1); - }); + // @returns {TestResult} + test (path) { + return this._test(path, this._testCache, true) + } +} - globTasks.push({ - pattern: pattern, - opts: objectAssign({}, opts, { - ignore: opts.ignore.concat(ignore) - }) - }); - }); +const factory = options => new Ignore(options) - return globTasks; -} +const returnFalse = () => false -module.exports = function (patterns, opts) { - var globTasks; +const isPathValid = path => + checkPath(path && checkPath.convert(path), path, returnFalse) - try { - globTasks = generateGlobTasks(patterns, opts); - } catch (err) { - return Promise.reject(err); - } +factory.isPathValid = isPathValid - return Promise.all(globTasks.map(function (task) { - return globP(task.pattern, task.opts); - })).then(function (paths) { - return arrayUnion.apply(null, paths); - }); -}; +// Fixes typescript +factory.default = factory -module.exports.sync = function (patterns, opts) { - var globTasks = generateGlobTasks(patterns, opts); +module.exports = factory - return globTasks.reduce(function (matches, task) { - return arrayUnion(matches, glob.sync(task.pattern, task.opts)); - }, []); -}; +// Windows +// -------------------------------------------------------------- +/* istanbul ignore if */ +if ( + // Detect `process` so that it can run in browsers. + typeof process !== 'undefined' + && ( + process.env && process.env.IGNORE_TEST_WIN32 + || process.platform === 'win32' + ) +) { + /* eslint no-control-regex: "off" */ + const makePosix = str => /^\\\\\?\\/.test(str) + || /["<>|\u0000-\u001F]+/u.test(str) + ? str + : str.replace(/\\/g, '/') -module.exports.generateGlobTasks = generateGlobTasks; + checkPath.convert = makePosix -module.exports.hasMagic = function (patterns, opts) { - return [].concat(patterns).some(function (pattern) { - return glob.hasMagic(pattern, opts); - }); -}; + // 'C:\\foo' <- 'C:\\foo' has been converted to 'C:/' + // 'd:\\foo' + const REGIX_IS_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i + checkPath.isNotRelative = path => + REGIX_IS_WINDOWS_PATH_ABSOLUTE.test(path) + || isNotRelative(path) +} /***/ }), -/* 168 */ +/* 248 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; +module.exports = path => { + const isExtendedLengthPath = /^\\\\\?\\/.test(path); + const hasNonAscii = /[^\u0000-\u0080]+/.test(path); // eslint-disable-line no-control-regex -module.exports = typeof Promise === 'function' ? Promise : __webpack_require__(169); + if (isExtendedLengthPath || hasNonAscii) { + return path; + } + + return path.replace(/\\/g, '/'); +}; /***/ }), -/* 169 */ +/* 249 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; +const {Transform} = __webpack_require__(28); -var PENDING = 'pending'; -var SETTLED = 'settled'; -var FULFILLED = 'fulfilled'; -var REJECTED = 'rejected'; -var NOOP = function () {}; -var isNode = typeof global !== 'undefined' && typeof global.process !== 'undefined' && typeof global.process.emit === 'function'; - -var asyncSetTimer = typeof setImmediate === 'undefined' ? setTimeout : setImmediate; -var asyncQueue = []; -var asyncTimer; - -function asyncFlush() { - // run promise callbacks - for (var i = 0; i < asyncQueue.length; i++) { - asyncQueue[i][0](asyncQueue[i][1]); +class ObjectTransform extends Transform { + constructor() { + super({ + objectMode: true + }); } - - // reset async asyncQueue - asyncQueue = []; - asyncTimer = false; } -function asyncCall(callback, arg) { - asyncQueue.push([callback, arg]); +class FilterStream extends ObjectTransform { + constructor(filter) { + super(); + this._filter = filter; + } + + _transform(data, encoding, callback) { + if (this._filter(data)) { + this.push(data); + } - if (!asyncTimer) { - asyncTimer = true; - asyncSetTimer(asyncFlush, 0); + callback(); } } -function invokeResolver(resolver, promise) { - function resolvePromise(value) { - resolve(promise, value); +class UniqueStream extends ObjectTransform { + constructor() { + super(); + this._pushed = new Set(); } - function rejectPromise(reason) { - reject(promise, reason); - } + _transform(data, encoding, callback) { + if (!this._pushed.has(data)) { + this.push(data); + this._pushed.add(data); + } - try { - resolver(resolvePromise, rejectPromise); - } catch (e) { - rejectPromise(e); + callback(); } } -function invokeCallback(subscriber) { - var owner = subscriber.owner; - var settled = owner._state; - var value = owner._data; - var callback = subscriber[settled]; - var promise = subscriber.then; +module.exports = { + FilterStream, + UniqueStream +}; - if (typeof callback === 'function') { - settled = FULFILLED; - try { - value = callback(value); - } catch (e) { - reject(promise, e); - } - } - if (!handleThenable(promise, value)) { - if (settled === FULFILLED) { - resolve(promise, value); - } +/***/ }), +/* 250 */ +/***/ (function(module, exports, __webpack_require__) { - if (settled === REJECTED) { - reject(promise, value); - } - } +var fs = __webpack_require__(23) +var polyfills = __webpack_require__(251) +var legacy = __webpack_require__(252) +var clone = __webpack_require__(253) + +var util = __webpack_require__(29) + +/* istanbul ignore next - node 0.x polyfill */ +var gracefulQueue +var previousSymbol + +/* istanbul ignore else - node 0.x polyfill */ +if (typeof Symbol === 'function' && typeof Symbol.for === 'function') { + gracefulQueue = Symbol.for('graceful-fs.queue') + // This is used in testing by future versions + previousSymbol = Symbol.for('graceful-fs.previous') +} else { + gracefulQueue = '___graceful-fs.queue' + previousSymbol = '___graceful-fs.previous' } -function handleThenable(promise, value) { - var resolved; +function noop () {} - try { - if (promise === value) { - throw new TypeError('A promises callback cannot return that same promise.'); - } +var debug = noop +if (util.debuglog) + debug = util.debuglog('gfs4') +else if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || '')) + debug = function() { + var m = util.format.apply(util, arguments) + m = 'GFS4: ' + m.split(/\n/).join('\nGFS4: ') + console.error(m) + } - if (value && (typeof value === 'function' || typeof value === 'object')) { - // then should be retrieved only once - var then = value.then; +// Once time initialization +if (!global[gracefulQueue]) { + // This queue can be shared by multiple loaded instances + var queue = [] + Object.defineProperty(global, gracefulQueue, { + get: function() { + return queue + } + }) - if (typeof then === 'function') { - then.call(value, function (val) { - if (!resolved) { - resolved = true; + // Patch fs.close/closeSync to shared queue version, because we need + // to retry() whenever a close happens *anywhere* in the program. + // This is essential when multiple graceful-fs instances are + // in play at the same time. + fs.close = (function (fs$close) { + function close (fd, cb) { + return fs$close.call(fs, fd, function (err) { + // This function uses the graceful-fs shared queue + if (!err) { + retry() + } - if (value === val) { - fulfill(promise, val); - } else { - resolve(promise, val); - } - } - }, function (reason) { - if (!resolved) { - resolved = true; + if (typeof cb === 'function') + cb.apply(this, arguments) + }) + } - reject(promise, reason); - } - }); + Object.defineProperty(close, previousSymbol, { + value: fs$close + }) + return close + })(fs.close) - return true; - } - } - } catch (e) { - if (!resolved) { - reject(promise, e); - } + fs.closeSync = (function (fs$closeSync) { + function closeSync (fd) { + // This function uses the graceful-fs shared queue + fs$closeSync.apply(fs, arguments) + retry() + } - return true; - } + Object.defineProperty(closeSync, previousSymbol, { + value: fs$closeSync + }) + return closeSync + })(fs.closeSync) - return false; + if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || '')) { + process.on('exit', function() { + debug(global[gracefulQueue]) + __webpack_require__(30).equal(global[gracefulQueue].length, 0) + }) + } } -function resolve(promise, value) { - if (promise === value || !handleThenable(promise, value)) { - fulfill(promise, value); - } +module.exports = patch(clone(fs)) +if (process.env.TEST_GRACEFUL_FS_GLOBAL_PATCH && !fs.__patched) { + module.exports = patch(fs) + fs.__patched = true; } -function fulfill(promise, value) { - if (promise._state === PENDING) { - promise._state = SETTLED; - promise._data = value; +function patch (fs) { + // Everything that references the open() function needs to be in here + polyfills(fs) + fs.gracefulify = patch - asyncCall(publishFulfillment, promise); - } -} + fs.createReadStream = createReadStream + fs.createWriteStream = createWriteStream + var fs$readFile = fs.readFile + fs.readFile = readFile + function readFile (path, options, cb) { + if (typeof options === 'function') + cb = options, options = null -function reject(promise, reason) { - if (promise._state === PENDING) { - promise._state = SETTLED; - promise._data = reason; + return go$readFile(path, options, cb) - asyncCall(publishRejection, promise); - } -} + function go$readFile (path, options, cb) { + return fs$readFile(path, options, function (err) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$readFile, [path, options, cb]]) + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + retry() + } + }) + } + } -function publish(promise) { - promise._then = promise._then.forEach(invokeCallback); -} + var fs$writeFile = fs.writeFile + fs.writeFile = writeFile + function writeFile (path, data, options, cb) { + if (typeof options === 'function') + cb = options, options = null -function publishFulfillment(promise) { - promise._state = FULFILLED; - publish(promise); -} + return go$writeFile(path, data, options, cb) -function publishRejection(promise) { - promise._state = REJECTED; - publish(promise); - if (!promise._handled && isNode) { - global.process.emit('unhandledRejection', promise._data, promise); - } -} + function go$writeFile (path, data, options, cb) { + return fs$writeFile(path, data, options, function (err) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$writeFile, [path, data, options, cb]]) + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + retry() + } + }) + } + } -function notifyRejectionHandled(promise) { - global.process.emit('rejectionHandled', promise); -} + var fs$appendFile = fs.appendFile + if (fs$appendFile) + fs.appendFile = appendFile + function appendFile (path, data, options, cb) { + if (typeof options === 'function') + cb = options, options = null -/** - * @class - */ -function Promise(resolver) { - if (typeof resolver !== 'function') { - throw new TypeError('Promise resolver ' + resolver + ' is not a function'); - } + return go$appendFile(path, data, options, cb) - if (this instanceof Promise === false) { - throw new TypeError('Failed to construct \'Promise\': Please use the \'new\' operator, this object constructor cannot be called as a function.'); - } + function go$appendFile (path, data, options, cb) { + return fs$appendFile(path, data, options, function (err) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$appendFile, [path, data, options, cb]]) + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + retry() + } + }) + } + } - this._then = []; + var fs$readdir = fs.readdir + fs.readdir = readdir + function readdir (path, options, cb) { + var args = [path] + if (typeof options !== 'function') { + args.push(options) + } else { + cb = options + } + args.push(go$readdir$cb) - invokeResolver(resolver, this); -} + return go$readdir(args) -Promise.prototype = { - constructor: Promise, + function go$readdir$cb (err, files) { + if (files && files.sort) + files.sort() - _state: PENDING, - _then: null, - _data: undefined, - _handled: false, + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$readdir, [args]]) - then: function (onFulfillment, onRejection) { - var subscriber = { - owner: this, - then: new this.constructor(NOOP), - fulfilled: onFulfillment, - rejected: onRejection - }; + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + retry() + } + } + } - if ((onRejection || onFulfillment) && !this._handled) { - this._handled = true; - if (this._state === REJECTED && isNode) { - asyncCall(notifyRejectionHandled, this); - } - } + function go$readdir (args) { + return fs$readdir.apply(fs, args) + } - if (this._state === FULFILLED || this._state === REJECTED) { - // already resolved, call callback async - asyncCall(invokeCallback, subscriber); - } else { - // subscribe - this._then.push(subscriber); - } + if (process.version.substr(0, 4) === 'v0.8') { + var legStreams = legacy(fs) + ReadStream = legStreams.ReadStream + WriteStream = legStreams.WriteStream + } - return subscriber.then; - }, + var fs$ReadStream = fs.ReadStream + if (fs$ReadStream) { + ReadStream.prototype = Object.create(fs$ReadStream.prototype) + ReadStream.prototype.open = ReadStream$open + } - catch: function (onRejection) { - return this.then(null, onRejection); - } -}; + var fs$WriteStream = fs.WriteStream + if (fs$WriteStream) { + WriteStream.prototype = Object.create(fs$WriteStream.prototype) + WriteStream.prototype.open = WriteStream$open + } -Promise.all = function (promises) { - if (!Array.isArray(promises)) { - throw new TypeError('You must pass an array to Promise.all().'); - } + Object.defineProperty(fs, 'ReadStream', { + get: function () { + return ReadStream + }, + set: function (val) { + ReadStream = val + }, + enumerable: true, + configurable: true + }) + Object.defineProperty(fs, 'WriteStream', { + get: function () { + return WriteStream + }, + set: function (val) { + WriteStream = val + }, + enumerable: true, + configurable: true + }) - return new Promise(function (resolve, reject) { - var results = []; - var remaining = 0; + // legacy names + Object.defineProperty(fs, 'FileReadStream', { + get: function () { + return ReadStream + }, + set: function (val) { + ReadStream = val + }, + enumerable: true, + configurable: true + }) + Object.defineProperty(fs, 'FileWriteStream', { + get: function () { + return WriteStream + }, + set: function (val) { + WriteStream = val + }, + enumerable: true, + configurable: true + }) - function resolver(index) { - remaining++; - return function (value) { - results[index] = value; - if (!--remaining) { - resolve(results); - } - }; - } + function ReadStream (path, options) { + if (this instanceof ReadStream) + return fs$ReadStream.apply(this, arguments), this + else + return ReadStream.apply(Object.create(ReadStream.prototype), arguments) + } - for (var i = 0, promise; i < promises.length; i++) { - promise = promises[i]; + function ReadStream$open () { + var that = this + open(that.path, that.flags, that.mode, function (err, fd) { + if (err) { + if (that.autoClose) + that.destroy() - if (promise && typeof promise.then === 'function') { - promise.then(resolver(i), reject); - } else { - results[i] = promise; - } - } + that.emit('error', err) + } else { + that.fd = fd + that.emit('open', fd) + that.read() + } + }) + } - if (!remaining) { - resolve(results); - } - }); -}; + function WriteStream (path, options) { + if (this instanceof WriteStream) + return fs$WriteStream.apply(this, arguments), this + else + return WriteStream.apply(Object.create(WriteStream.prototype), arguments) + } -Promise.race = function (promises) { - if (!Array.isArray(promises)) { - throw new TypeError('You must pass an array to Promise.race().'); - } + function WriteStream$open () { + var that = this + open(that.path, that.flags, that.mode, function (err, fd) { + if (err) { + that.destroy() + that.emit('error', err) + } else { + that.fd = fd + that.emit('open', fd) + } + }) + } - return new Promise(function (resolve, reject) { - for (var i = 0, promise; i < promises.length; i++) { - promise = promises[i]; + function createReadStream (path, options) { + return new fs.ReadStream(path, options) + } - if (promise && typeof promise.then === 'function') { - promise.then(resolve, reject); - } else { - resolve(promise); - } - } - }); -}; + function createWriteStream (path, options) { + return new fs.WriteStream(path, options) + } -Promise.resolve = function (value) { - if (value && typeof value === 'object' && value.constructor === Promise) { - return value; - } + var fs$open = fs.open + fs.open = open + function open (path, flags, mode, cb) { + if (typeof mode === 'function') + cb = mode, mode = null - return new Promise(function (resolve) { - resolve(value); - }); -}; + return go$open(path, flags, mode, cb) -Promise.reject = function (reason) { - return new Promise(function (resolve, reject) { - reject(reason); - }); -}; + function go$open (path, flags, mode, cb) { + return fs$open(path, flags, mode, function (err, fd) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$open, [path, flags, mode, cb]]) + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + retry() + } + }) + } + } + + return fs +} + +function enqueue (elem) { + debug('ENQUEUE', elem[0].name, elem[1]) + global[gracefulQueue].push(elem) +} -module.exports = Promise; +function retry () { + var elem = global[gracefulQueue].shift() + if (elem) { + debug('RETRY', elem[0].name, elem[1]) + elem[0].apply(null, elem[1]) + } +} /***/ }), -/* 170 */ +/* 251 */ /***/ (function(module, exports, __webpack_require__) { -"use strict"; +var constants = __webpack_require__(26) -var arrayUniq = __webpack_require__(171); +var origCwd = process.cwd +var cwd = null -module.exports = function () { - return arrayUniq([].concat.apply([], arguments)); -}; +var platform = process.env.GRACEFUL_FS_PLATFORM || process.platform +process.cwd = function() { + if (!cwd) + cwd = origCwd.call(process) + return cwd +} +try { + process.cwd() +} catch (er) {} -/***/ }), -/* 171 */ -/***/ (function(module, exports, __webpack_require__) { +var chdir = process.chdir +process.chdir = function(d) { + cwd = null + chdir.call(process, d) +} -"use strict"; +module.exports = patch +function patch (fs) { + // (re-)implement some things that are known busted or missing. -// there's 3 implementations written in increasing order of efficiency + // lchmod, broken prior to 0.6.2 + // back-port the fix here. + if (constants.hasOwnProperty('O_SYMLINK') && + process.version.match(/^v0\.6\.[0-2]|^v0\.5\./)) { + patchLchmod(fs) + } -// 1 - no Set type is defined -function uniqNoSet(arr) { - var ret = []; + // lutimes implementation, or no-op + if (!fs.lutimes) { + patchLutimes(fs) + } - for (var i = 0; i < arr.length; i++) { - if (ret.indexOf(arr[i]) === -1) { - ret.push(arr[i]); - } - } + // https://github.com/isaacs/node-graceful-fs/issues/4 + // Chown should not fail on einval or eperm if non-root. + // It should not fail on enosys ever, as this just indicates + // that a fs doesn't support the intended operation. - return ret; -} + fs.chown = chownFix(fs.chown) + fs.fchown = chownFix(fs.fchown) + fs.lchown = chownFix(fs.lchown) -// 2 - a simple Set type is defined -function uniqSet(arr) { - var seen = new Set(); - return arr.filter(function (el) { - if (!seen.has(el)) { - seen.add(el); - return true; - } + fs.chmod = chmodFix(fs.chmod) + fs.fchmod = chmodFix(fs.fchmod) + fs.lchmod = chmodFix(fs.lchmod) - return false; - }); -} + fs.chownSync = chownFixSync(fs.chownSync) + fs.fchownSync = chownFixSync(fs.fchownSync) + fs.lchownSync = chownFixSync(fs.lchownSync) + + fs.chmodSync = chmodFixSync(fs.chmodSync) + fs.fchmodSync = chmodFixSync(fs.fchmodSync) + fs.lchmodSync = chmodFixSync(fs.lchmodSync) + + fs.stat = statFix(fs.stat) + fs.fstat = statFix(fs.fstat) + fs.lstat = statFix(fs.lstat) + + fs.statSync = statFixSync(fs.statSync) + fs.fstatSync = statFixSync(fs.fstatSync) + fs.lstatSync = statFixSync(fs.lstatSync) + + // if lchmod/lchown do not exist, then make them no-ops + if (!fs.lchmod) { + fs.lchmod = function (path, mode, cb) { + if (cb) process.nextTick(cb) + } + fs.lchmodSync = function () {} + } + if (!fs.lchown) { + fs.lchown = function (path, uid, gid, cb) { + if (cb) process.nextTick(cb) + } + fs.lchownSync = function () {} + } + + // on Windows, A/V software can lock the directory, causing this + // to fail with an EACCES or EPERM if the directory contains newly + // created files. Try again on failure, for up to 60 seconds. + + // Set the timeout this long because some Windows Anti-Virus, such as Parity + // bit9, may lock files for up to a minute, causing npm package install + // failures. Also, take care to yield the scheduler. Windows scheduling gives + // CPU to a busy looping process, which can cause the program causing the lock + // contention to be starved of CPU by node, so the contention doesn't resolve. + if (platform === "win32") { + fs.rename = (function (fs$rename) { return function (from, to, cb) { + var start = Date.now() + var backoff = 0; + fs$rename(from, to, function CB (er) { + if (er + && (er.code === "EACCES" || er.code === "EPERM") + && Date.now() - start < 60000) { + setTimeout(function() { + fs.stat(to, function (stater, st) { + if (stater && stater.code === "ENOENT") + fs$rename(from, to, CB); + else + cb(er) + }) + }, backoff) + if (backoff < 100) + backoff += 10; + return; + } + if (cb) cb(er) + }) + }})(fs.rename) + } -// 3 - a standard Set type is defined and it has a forEach method -function uniqSetWithForEach(arr) { - var ret = []; + // if read() returns EAGAIN, then just try it again. + fs.read = (function (fs$read) { + function read (fd, buffer, offset, length, position, callback_) { + var callback + if (callback_ && typeof callback_ === 'function') { + var eagCounter = 0 + callback = function (er, _, __) { + if (er && er.code === 'EAGAIN' && eagCounter < 10) { + eagCounter ++ + return fs$read.call(fs, fd, buffer, offset, length, position, callback) + } + callback_.apply(this, arguments) + } + } + return fs$read.call(fs, fd, buffer, offset, length, position, callback) + } - (new Set(arr)).forEach(function (el) { - ret.push(el); - }); + // This ensures `util.promisify` works as it does for native `fs.read`. + read.__proto__ = fs$read + return read + })(fs.read) - return ret; -} + fs.readSync = (function (fs$readSync) { return function (fd, buffer, offset, length, position) { + var eagCounter = 0 + while (true) { + try { + return fs$readSync.call(fs, fd, buffer, offset, length, position) + } catch (er) { + if (er.code === 'EAGAIN' && eagCounter < 10) { + eagCounter ++ + continue + } + throw er + } + } + }})(fs.readSync) -// V8 currently has a broken implementation -// https://github.com/joyent/node/issues/8449 -function doesForEachActuallyWork() { - var ret = false; + function patchLchmod (fs) { + fs.lchmod = function (path, mode, callback) { + fs.open( path + , constants.O_WRONLY | constants.O_SYMLINK + , mode + , function (err, fd) { + if (err) { + if (callback) callback(err) + return + } + // prefer to return the chmod error, if one occurs, + // but still try to close, and report closing errors if they occur. + fs.fchmod(fd, mode, function (err) { + fs.close(fd, function(err2) { + if (callback) callback(err || err2) + }) + }) + }) + } - (new Set([true])).forEach(function (el) { - ret = el; - }); + fs.lchmodSync = function (path, mode) { + var fd = fs.openSync(path, constants.O_WRONLY | constants.O_SYMLINK, mode) - return ret === true; -} + // prefer to return the chmod error, if one occurs, + // but still try to close, and report closing errors if they occur. + var threw = true + var ret + try { + ret = fs.fchmodSync(fd, mode) + threw = false + } finally { + if (threw) { + try { + fs.closeSync(fd) + } catch (er) {} + } else { + fs.closeSync(fd) + } + } + return ret + } + } -if ('Set' in global) { - if (typeof Set.prototype.forEach === 'function' && doesForEachActuallyWork()) { - module.exports = uniqSetWithForEach; - } else { - module.exports = uniqSet; - } -} else { - module.exports = uniqNoSet; -} + function patchLutimes (fs) { + if (constants.hasOwnProperty("O_SYMLINK")) { + fs.lutimes = function (path, at, mt, cb) { + fs.open(path, constants.O_SYMLINK, function (er, fd) { + if (er) { + if (cb) cb(er) + return + } + fs.futimes(fd, at, mt, function (er) { + fs.close(fd, function (er2) { + if (cb) cb(er || er2) + }) + }) + }) + } + fs.lutimesSync = function (path, at, mt) { + var fd = fs.openSync(path, constants.O_SYMLINK) + var ret + var threw = true + try { + ret = fs.futimesSync(fd, at, mt) + threw = false + } finally { + if (threw) { + try { + fs.closeSync(fd) + } catch (er) {} + } else { + fs.closeSync(fd) + } + } + return ret + } -/***/ }), -/* 172 */ -/***/ (function(module, exports, __webpack_require__) { + } else { + fs.lutimes = function (_a, _b, _c, cb) { if (cb) process.nextTick(cb) } + fs.lutimesSync = function () {} + } + } -"use strict"; -/* -object-assign -(c) Sindre Sorhus -@license MIT -*/ + function chmodFix (orig) { + if (!orig) return orig + return function (target, mode, cb) { + return orig.call(fs, target, mode, function (er) { + if (chownErOk(er)) er = null + if (cb) cb.apply(this, arguments) + }) + } + } + function chmodFixSync (orig) { + if (!orig) return orig + return function (target, mode) { + try { + return orig.call(fs, target, mode) + } catch (er) { + if (!chownErOk(er)) throw er + } + } + } -/* eslint-disable no-unused-vars */ -var getOwnPropertySymbols = Object.getOwnPropertySymbols; -var hasOwnProperty = Object.prototype.hasOwnProperty; -var propIsEnumerable = Object.prototype.propertyIsEnumerable; -function toObject(val) { - if (val === null || val === undefined) { - throw new TypeError('Object.assign cannot be called with null or undefined'); - } + function chownFix (orig) { + if (!orig) return orig + return function (target, uid, gid, cb) { + return orig.call(fs, target, uid, gid, function (er) { + if (chownErOk(er)) er = null + if (cb) cb.apply(this, arguments) + }) + } + } - return Object(val); -} + function chownFixSync (orig) { + if (!orig) return orig + return function (target, uid, gid) { + try { + return orig.call(fs, target, uid, gid) + } catch (er) { + if (!chownErOk(er)) throw er + } + } + } -function shouldUseNative() { - try { - if (!Object.assign) { - return false; - } + function statFix (orig) { + if (!orig) return orig + // Older versions of Node erroneously returned signed integers for + // uid + gid. + return function (target, options, cb) { + if (typeof options === 'function') { + cb = options + options = null + } + function callback (er, stats) { + if (stats) { + if (stats.uid < 0) stats.uid += 0x100000000 + if (stats.gid < 0) stats.gid += 0x100000000 + } + if (cb) cb.apply(this, arguments) + } + return options ? orig.call(fs, target, options, callback) + : orig.call(fs, target, callback) + } + } - // Detect buggy property enumeration order in older V8 versions. + function statFixSync (orig) { + if (!orig) return orig + // Older versions of Node erroneously returned signed integers for + // uid + gid. + return function (target, options) { + var stats = options ? orig.call(fs, target, options) + : orig.call(fs, target) + if (stats.uid < 0) stats.uid += 0x100000000 + if (stats.gid < 0) stats.gid += 0x100000000 + return stats; + } + } - // https://bugs.chromium.org/p/v8/issues/detail?id=4118 - var test1 = new String('abc'); // eslint-disable-line no-new-wrappers - test1[5] = 'de'; - if (Object.getOwnPropertyNames(test1)[0] === '5') { - return false; - } + // ENOSYS means that the fs doesn't support the op. Just ignore + // that, because it doesn't matter. + // + // if there's no getuid, or if getuid() is something other + // than 0, and the error is EINVAL or EPERM, then just ignore + // it. + // + // This specific case is a silent failure in cp, install, tar, + // and most other unix tools that manage permissions. + // + // When running as root, or if other types of errors are + // encountered, then it's strict. + function chownErOk (er) { + if (!er) + return true - // https://bugs.chromium.org/p/v8/issues/detail?id=3056 - var test2 = {}; - for (var i = 0; i < 10; i++) { - test2['_' + String.fromCharCode(i)] = i; - } - var order2 = Object.getOwnPropertyNames(test2).map(function (n) { - return test2[n]; - }); - if (order2.join('') !== '0123456789') { - return false; - } + if (er.code === "ENOSYS") + return true - // https://bugs.chromium.org/p/v8/issues/detail?id=3056 - var test3 = {}; - 'abcdefghijklmnopqrst'.split('').forEach(function (letter) { - test3[letter] = letter; - }); - if (Object.keys(Object.assign({}, test3)).join('') !== - 'abcdefghijklmnopqrst') { - return false; - } + var nonroot = !process.getuid || process.getuid() !== 0 + if (nonroot) { + if (er.code === "EINVAL" || er.code === "EPERM") + return true + } - return true; - } catch (err) { - // We don't expect any of the above to throw, but better to be safe. - return false; - } + return false + } } -module.exports = shouldUseNative() ? Object.assign : function (target, source) { - var from; - var to = toObject(target); - var symbols; - - for (var s = 1; s < arguments.length; s++) { - from = Object(arguments[s]); - - for (var key in from) { - if (hasOwnProperty.call(from, key)) { - to[key] = from[key]; - } - } - - if (getOwnPropertySymbols) { - symbols = getOwnPropertySymbols(from); - for (var i = 0; i < symbols.length; i++) { - if (propIsEnumerable.call(from, symbols[i])) { - to[symbols[i]] = from[symbols[i]]; - } - } - } - } - - return to; -}; - /***/ }), -/* 173 */ +/* 252 */ /***/ (function(module, exports, __webpack_require__) { -"use strict"; - +var Stream = __webpack_require__(28).Stream -var processFn = function (fn, P, opts) { - return function () { - var that = this; - var args = new Array(arguments.length); +module.exports = legacy - for (var i = 0; i < arguments.length; i++) { - args[i] = arguments[i]; - } +function legacy (fs) { + return { + ReadStream: ReadStream, + WriteStream: WriteStream + } - return new P(function (resolve, reject) { - args.push(function (err, result) { - if (err) { - reject(err); - } else if (opts.multiArgs) { - var results = new Array(arguments.length - 1); + function ReadStream (path, options) { + if (!(this instanceof ReadStream)) return new ReadStream(path, options); - for (var i = 1; i < arguments.length; i++) { - results[i - 1] = arguments[i]; - } + Stream.call(this); - resolve(results); - } else { - resolve(result); - } - }); + var self = this; - fn.apply(that, args); - }); - }; -}; + this.path = path; + this.fd = null; + this.readable = true; + this.paused = false; -var pify = module.exports = function (obj, P, opts) { - if (typeof P !== 'function') { - opts = P; - P = Promise; - } + this.flags = 'r'; + this.mode = 438; /*=0666*/ + this.bufferSize = 64 * 1024; - opts = opts || {}; - opts.exclude = opts.exclude || [/.+Sync$/]; + options = options || {}; - var filter = function (key) { - var match = function (pattern) { - return typeof pattern === 'string' ? key === pattern : pattern.test(key); - }; + // Mixin options into this + var keys = Object.keys(options); + for (var index = 0, length = keys.length; index < length; index++) { + var key = keys[index]; + this[key] = options[key]; + } - return opts.include ? opts.include.some(match) : !opts.exclude.some(match); - }; + if (this.encoding) this.setEncoding(this.encoding); - var ret = typeof obj === 'function' ? function () { - if (opts.excludeMain) { - return obj.apply(this, arguments); - } + if (this.start !== undefined) { + if ('number' !== typeof this.start) { + throw TypeError('start must be a Number'); + } + if (this.end === undefined) { + this.end = Infinity; + } else if ('number' !== typeof this.end) { + throw TypeError('end must be a Number'); + } - return processFn(obj, P, opts).apply(this, arguments); - } : {}; + if (this.start > this.end) { + throw new Error('start must be <= end'); + } - return Object.keys(obj).reduce(function (ret, key) { - var x = obj[key]; + this.pos = this.start; + } - ret[key] = typeof x === 'function' && filter(key) ? processFn(x, P, opts) : x; + if (this.fd !== null) { + process.nextTick(function() { + self._read(); + }); + return; + } - return ret; - }, ret); -}; + fs.open(this.path, this.flags, this.mode, function (err, fd) { + if (err) { + self.emit('error', err); + self.readable = false; + return; + } -pify.all = pify; + self.fd = fd; + self.emit('open', fd); + self._read(); + }) + } + function WriteStream (path, options) { + if (!(this instanceof WriteStream)) return new WriteStream(path, options); -/***/ }), -/* 174 */ -/***/ (function(module, exports, __webpack_require__) { + Stream.call(this); -"use strict"; + this.path = path; + this.fd = null; + this.writable = true; -const path = __webpack_require__(16); + this.flags = 'w'; + this.encoding = 'binary'; + this.mode = 438; /*=0666*/ + this.bytesWritten = 0; -module.exports = path_ => path.resolve(path_) === process.cwd(); + options = options || {}; + // Mixin options into this + var keys = Object.keys(options); + for (var index = 0, length = keys.length; index < length; index++) { + var key = keys[index]; + this[key] = options[key]; + } -/***/ }), -/* 175 */ -/***/ (function(module, exports, __webpack_require__) { + if (this.start !== undefined) { + if ('number' !== typeof this.start) { + throw TypeError('start must be a Number'); + } + if (this.start < 0) { + throw new Error('start must be >= zero'); + } -"use strict"; + this.pos = this.start; + } -const isPathInside = __webpack_require__(176); + this.busy = false; + this._queue = []; -module.exports = path => isPathInside(path, process.cwd()); + if (this.fd === null) { + this._open = fs.open; + this._queue.push([this._open, this.path, this.flags, this.mode, undefined]); + this.flush(); + } + } +} /***/ }), -/* 176 */ +/* 253 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const path = __webpack_require__(16); -const pathIsInside = __webpack_require__(177); -module.exports = (childPath, parentPath) => { - childPath = path.resolve(childPath); - parentPath = path.resolve(parentPath); +module.exports = clone - if (childPath === parentPath) { - return false; - } +function clone (obj) { + if (obj === null || typeof obj !== 'object') + return obj - return pathIsInside(childPath, parentPath); -}; + if (obj instanceof Object) + var copy = { __proto__: obj.__proto__ } + else + var copy = Object.create(null) + + Object.getOwnPropertyNames(obj).forEach(function (key) { + Object.defineProperty(copy, key, Object.getOwnPropertyDescriptor(obj, key)) + }) + + return copy +} /***/ }), -/* 177 */ +/* 254 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; +const path = __webpack_require__(16); -var path = __webpack_require__(16); +module.exports = path_ => { + let cwd = process.cwd(); -module.exports = function (thePath, potentialParent) { - // For inside-directory checking, we want to allow trailing slashes, so normalize. - thePath = stripTrailingSep(thePath); - potentialParent = stripTrailingSep(potentialParent); + path_ = path.resolve(path_); - // Node treats only Windows as case-insensitive in its path module; we follow those conventions. - if (process.platform === "win32") { - thePath = thePath.toLowerCase(); - potentialParent = potentialParent.toLowerCase(); - } + if (process.platform === 'win32') { + cwd = cwd.toLowerCase(); + path_ = path_.toLowerCase(); + } - return thePath.lastIndexOf(potentialParent, 0) === 0 && - ( - thePath[potentialParent.length] === path.sep || - thePath[potentialParent.length] === undefined - ); + return path_ === cwd; }; -function stripTrailingSep(thePath) { - if (thePath[thePath.length - 1] === path.sep) { - return thePath.slice(0, -1); - } - return thePath; -} - /***/ }), -/* 178 */ +/* 255 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; +const path = __webpack_require__(16); -const processFn = (fn, options) => function (...args) { - const P = options.promiseModule; - - return new P((resolve, reject) => { - if (options.multiArgs) { - args.push((...result) => { - if (options.errorFirst) { - if (result[0]) { - reject(result); - } else { - result.shift(); - resolve(result); - } - } else { - resolve(result); - } - }); - } else if (options.errorFirst) { - args.push((error, result) => { - if (error) { - reject(error); - } else { - resolve(result); - } - }); - } else { - args.push(resolve); - } - - fn.apply(this, args); - }); -}; - -module.exports = (input, options) => { - options = Object.assign({ - exclude: [/.+(Sync|Stream)$/], - errorFirst: true, - promiseModule: Promise - }, options); +module.exports = (childPath, parentPath) => { + childPath = path.resolve(childPath); + parentPath = path.resolve(parentPath); - const objType = typeof input; - if (!(input !== null && (objType === 'object' || objType === 'function'))) { - throw new TypeError(`Expected \`input\` to be a \`Function\` or \`Object\`, got \`${input === null ? 'null' : objType}\``); + if (process.platform === 'win32') { + childPath = childPath.toLowerCase(); + parentPath = parentPath.toLowerCase(); } - const filter = key => { - const match = pattern => typeof pattern === 'string' ? key === pattern : pattern.test(key); - return options.include ? options.include.some(match) : !options.exclude.some(match); - }; - - let ret; - if (objType === 'function') { - ret = function (...args) { - return options.excludeMain ? input(...args) : processFn(input, options).apply(this, args); - }; - } else { - ret = Object.create(Object.getPrototypeOf(input)); + if (childPath === parentPath) { + return false; } - for (const key in input) { // eslint-disable-line guard-for-in - const property = input[key]; - ret[key] = typeof property === 'function' && filter(key) ? processFn(property, options) : property; - } + childPath += path.sep; + parentPath += path.sep; - return ret; + return childPath.startsWith(parentPath); }; /***/ }), -/* 179 */ +/* 256 */ /***/ (function(module, exports, __webpack_require__) { -module.exports = rimraf -rimraf.sync = rimrafSync - -var assert = __webpack_require__(30) -var path = __webpack_require__(16) -var fs = __webpack_require__(23) -var glob = __webpack_require__(37) -var _0666 = parseInt('666', 8) +const assert = __webpack_require__(30) +const path = __webpack_require__(16) +const fs = __webpack_require__(23) +let glob = undefined +try { + glob = __webpack_require__(37) +} catch (_err) { + // treat glob as optional. +} -var defaultGlobOpts = { +const defaultGlobOpts = { nosort: true, silent: true } // for EMFILE handling -var timeout = 0 +let timeout = 0 -var isWindows = (process.platform === "win32") +const isWindows = (process.platform === "win32") -function defaults (options) { - var methods = [ +const defaults = options => { + const methods = [ 'unlink', 'chmod', 'stat', @@ -24803,7 +31504,7 @@ function defaults (options) { 'rmdir', 'readdir' ] - methods.forEach(function(m) { + methods.forEach(m => { options[m] = options[m] || fs[m] m = m + 'Sync' options[m] = options[m] || fs[m] @@ -24814,11 +31515,14 @@ function defaults (options) { if (options.glob === false) { options.disableGlob = true } + if (options.disableGlob !== true && glob === undefined) { + throw Error('glob dependency not found, set `options.disableGlob = true` if intentional') + } options.disableGlob = options.disableGlob || false options.glob = options.glob || defaultGlobOpts } -function rimraf (p, options, cb) { +const rimraf = (p, options, cb) => { if (typeof options === 'function') { cb = options options = {} @@ -24832,27 +31536,17 @@ function rimraf (p, options, cb) { defaults(options) - var busyTries = 0 - var errState = null - var n = 0 - - if (options.disableGlob || !glob.hasMagic(p)) - return afterGlob(null, [p]) - - options.lstat(p, function (er, stat) { - if (!er) - return afterGlob(null, [p]) - - glob(p, options.glob, afterGlob) - }) + let busyTries = 0 + let errState = null + let n = 0 - function next (er) { + const next = (er) => { errState = errState || er if (--n === 0) cb(errState) } - function afterGlob (er, results) { + const afterGlob = (er, results) => { if (er) return cb(er) @@ -24860,24 +31554,19 @@ function rimraf (p, options, cb) { if (n === 0) return cb() - results.forEach(function (p) { - rimraf_(p, options, function CB (er) { + results.forEach(p => { + const CB = (er) => { if (er) { if ((er.code === "EBUSY" || er.code === "ENOTEMPTY" || er.code === "EPERM") && busyTries < options.maxBusyTries) { busyTries ++ - var time = busyTries * 100 // try again, with the same exact callback as this one. - return setTimeout(function () { - rimraf_(p, options, CB) - }, time) + return setTimeout(() => rimraf_(p, options, CB), busyTries * 100) } // this one won't happen if graceful-fs is used. if (er.code === "EMFILE" && timeout < options.emfileWait) { - return setTimeout(function () { - rimraf_(p, options, CB) - }, timeout ++) + return setTimeout(() => rimraf_(p, options, CB), timeout ++) } // already gone @@ -24886,9 +31575,21 @@ function rimraf (p, options, cb) { timeout = 0 next(er) - }) + } + rimraf_(p, options, CB) }) } + + if (options.disableGlob || !glob.hasMagic(p)) + return afterGlob(null, [p]) + + options.lstat(p, (er, stat) => { + if (!er) + return afterGlob(null, [p]) + + glob(p, options.glob, afterGlob) + }) + } // Two possible strategies. @@ -24902,14 +31603,14 @@ function rimraf (p, options, cb) { // // If anyone ever complains about this, then I guess the strategy could // be made configurable somehow. But until then, YAGNI. -function rimraf_ (p, options, cb) { +const rimraf_ = (p, options, cb) => { assert(p) assert(options) assert(typeof cb === 'function') // sunos lets the root user unlink directories, which is... weird. // so we have to lstat here and make sure it's not a dir. - options.lstat(p, function (er, st) { + options.lstat(p, (er, st) => { if (er && er.code === "ENOENT") return cb(null) @@ -24920,7 +31621,7 @@ function rimraf_ (p, options, cb) { if (st && st.isDirectory()) return rmdir(p, options, er, cb) - options.unlink(p, function (er) { + options.unlink(p, er => { if (er) { if (er.code === "ENOENT") return cb(null) @@ -24936,18 +31637,18 @@ function rimraf_ (p, options, cb) { }) } -function fixWinEPERM (p, options, er, cb) { +const fixWinEPERM = (p, options, er, cb) => { assert(p) assert(options) assert(typeof cb === 'function') if (er) assert(er instanceof Error) - options.chmod(p, _0666, function (er2) { + options.chmod(p, 0o666, er2 => { if (er2) cb(er2.code === "ENOENT" ? null : er) else - options.stat(p, function(er3, stats) { + options.stat(p, (er3, stats) => { if (er3) cb(er3.code === "ENOENT" ? null : er) else if (stats.isDirectory()) @@ -24958,14 +31659,14 @@ function fixWinEPERM (p, options, er, cb) { }) } -function fixWinEPERMSync (p, options, er) { +const fixWinEPERMSync = (p, options, er) => { assert(p) assert(options) if (er) assert(er instanceof Error) try { - options.chmodSync(p, _0666) + options.chmodSync(p, 0o666) } catch (er2) { if (er2.code === "ENOENT") return @@ -24973,8 +31674,9 @@ function fixWinEPERMSync (p, options, er) { throw er } + let stats try { - var stats = options.statSync(p) + stats = options.statSync(p) } catch (er3) { if (er3.code === "ENOENT") return @@ -24988,7 +31690,7 @@ function fixWinEPERMSync (p, options, er) { options.unlinkSync(p) } -function rmdir (p, options, originalEr, cb) { +const rmdir = (p, options, originalEr, cb) => { assert(p) assert(options) if (originalEr) @@ -24998,7 +31700,7 @@ function rmdir (p, options, originalEr, cb) { // try to rmdir first, and only readdir on ENOTEMPTY or EEXIST (SunOS) // if we guessed wrong, and it's not a directory, then // raise the original error. - options.rmdir(p, function (er) { + options.rmdir(p, er => { if (er && (er.code === "ENOTEMPTY" || er.code === "EEXIST" || er.code === "EPERM")) rmkids(p, options, cb) else if (er && er.code === "ENOTDIR") @@ -25008,20 +31710,20 @@ function rmdir (p, options, originalEr, cb) { }) } -function rmkids(p, options, cb) { +const rmkids = (p, options, cb) => { assert(p) assert(options) assert(typeof cb === 'function') - options.readdir(p, function (er, files) { + options.readdir(p, (er, files) => { if (er) return cb(er) - var n = files.length + let n = files.length if (n === 0) return options.rmdir(p, cb) - var errState - files.forEach(function (f) { - rimraf(path.join(p, f), options, function (er) { + let errState + files.forEach(f => { + rimraf(path.join(p, f), options, er => { if (errState) return if (er) @@ -25036,7 +31738,7 @@ function rmkids(p, options, cb) { // this looks simpler, and is strictly *faster*, but will // tie up the JavaScript thread and fail on excessively // deep directory trees. -function rimrafSync (p, options) { +const rimrafSync = (p, options) => { options = options || {} defaults(options) @@ -25045,7 +31747,7 @@ function rimrafSync (p, options) { assert(options, 'rimraf: missing options') assert.equal(typeof options, 'object', 'rimraf: options should be object') - var results + let results if (options.disableGlob || !glob.hasMagic(p)) { results = [p] @@ -25061,11 +31763,12 @@ function rimrafSync (p, options) { if (!results.length) return - for (var i = 0; i < results.length; i++) { - var p = results[i] + for (let i = 0; i < results.length; i++) { + const p = results[i] + let st try { - var st = options.lstatSync(p) + st = options.lstatSync(p) } catch (er) { if (er.code === "ENOENT") return @@ -25094,7 +31797,7 @@ function rimrafSync (p, options) { } } -function rmdirSync (p, options, originalEr) { +const rmdirSync = (p, options, originalEr) => { assert(p) assert(options) if (originalEr) @@ -25112,12 +31815,10 @@ function rmdirSync (p, options, originalEr) { } } -function rmkidsSync (p, options) { +const rmkidsSync = (p, options) => { assert(p) assert(options) - options.readdirSync(p).forEach(function (f) { - rimrafSync(path.join(p, f), options) - }) + options.readdirSync(p).forEach(f => rimrafSync(path.join(p, f), options)) // We only end up here once we got ENOTEMPTY at least once, and // at this point, we are guaranteed to have removed all the kids. @@ -25125,12 +31826,12 @@ function rmkidsSync (p, options) { // try really hard to delete stuff on windows, because it has a // PROFOUNDLY annoying habit of not closing handles promptly when // files are deleted, resulting in spurious ENOTEMPTY errors. - var retries = isWindows ? 100 : 1 - var i = 0 + const retries = isWindows ? 100 : 1 + let i = 0 do { - var threw = true + let threw = true try { - var ret = options.rmdirSync(p, options) + const ret = options.rmdirSync(p, options) threw = false return ret } finally { @@ -25140,96 +31841,243 @@ function rmkidsSync (p, options) { } while (true) } +module.exports = rimraf +rimraf.sync = rimrafSync + /***/ }), -/* 180 */ +/* 257 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; +const AggregateError = __webpack_require__(258); -const pMap = (iterable, mapper, options) => new Promise((resolve, reject) => { - options = Object.assign({ - concurrency: Infinity - }, options); - - if (typeof mapper !== 'function') { - throw new TypeError('Mapper function is required'); - } +module.exports = async ( + iterable, + mapper, + { + concurrency = Infinity, + stopOnError = true + } = {} +) => { + return new Promise((resolve, reject) => { + if (typeof mapper !== 'function') { + throw new TypeError('Mapper function is required'); + } - const {concurrency} = options; + if (!(typeof concurrency === 'number' && concurrency >= 1)) { + throw new TypeError(`Expected \`concurrency\` to be a number from 1 and up, got \`${concurrency}\` (${typeof concurrency})`); + } - if (!(typeof concurrency === 'number' && concurrency >= 1)) { - throw new TypeError(`Expected \`concurrency\` to be a number from 1 and up, got \`${concurrency}\` (${typeof concurrency})`); - } + const ret = []; + const errors = []; + const iterator = iterable[Symbol.iterator](); + let isRejected = false; + let isIterableDone = false; + let resolvingCount = 0; + let currentIndex = 0; - const ret = []; - const iterator = iterable[Symbol.iterator](); - let isRejected = false; - let isIterableDone = false; - let resolvingCount = 0; - let currentIndex = 0; + const next = () => { + if (isRejected) { + return; + } - const next = () => { - if (isRejected) { - return; - } + const nextItem = iterator.next(); + const i = currentIndex; + currentIndex++; - const nextItem = iterator.next(); - const i = currentIndex; - currentIndex++; + if (nextItem.done) { + isIterableDone = true; - if (nextItem.done) { - isIterableDone = true; + if (resolvingCount === 0) { + if (!stopOnError && errors.length !== 0) { + reject(new AggregateError(errors)); + } else { + resolve(ret); + } + } - if (resolvingCount === 0) { - resolve(ret); + return; } - return; - } - - resolvingCount++; + resolvingCount++; - Promise.resolve(nextItem.value) - .then(element => mapper(element, i)) - .then( - value => { - ret[i] = value; + (async () => { + try { + const element = await nextItem.value; + ret[i] = await mapper(element, i); resolvingCount--; next(); - }, - error => { - isRejected = true; - reject(error); + } catch (error) { + if (stopOnError) { + isRejected = true; + reject(error); + } else { + errors.push(error); + resolvingCount--; + next(); + } } - ); - }; + })(); + }; - for (let i = 0; i < concurrency; i++) { - next(); + for (let i = 0; i < concurrency; i++) { + next(); - if (isIterableDone) { - break; + if (isIterableDone) { + break; + } } + }); +}; + + +/***/ }), +/* 258 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const indentString = __webpack_require__(259); +const cleanStack = __webpack_require__(260); + +const cleanInternalStack = stack => stack.replace(/\s+at .*aggregate-error\/index.js:\d+:\d+\)?/g, ''); + +class AggregateError extends Error { + constructor(errors) { + if (!Array.isArray(errors)) { + throw new TypeError(`Expected input to be an Array, got ${typeof errors}`); + } + + errors = [...errors].map(error => { + if (error instanceof Error) { + return error; + } + + if (error !== null && typeof error === 'object') { + // Handle plain error objects with message property and/or possibly other metadata + return Object.assign(new Error(error.message), error); + } + + return new Error(error); + }); + + let message = errors + .map(error => { + // The `stack` property is not standardized, so we can't assume it exists + return typeof error.stack === 'string' ? cleanInternalStack(cleanStack(error.stack)) : String(error); + }) + .join('\n'); + message = '\n' + indentString(message, 4); + super(message); + + this.name = 'AggregateError'; + + Object.defineProperty(this, '_errors', {value: errors}); } -}); -module.exports = pMap; -// TODO: Remove this for the next major release -module.exports.default = pMap; + * [Symbol.iterator]() { + for (const error of this._errors) { + yield error; + } + } +} + +module.exports = AggregateError; /***/ }), -/* 181 */ +/* 259 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const chalk = __webpack_require__(182); -const cliCursor = __webpack_require__(186); -const cliSpinners = __webpack_require__(190); -const logSymbols = __webpack_require__(150); +module.exports = (str, count, opts) => { + // Support older versions: use the third parameter as options.indent + // TODO: Remove the workaround in the next major version + const options = typeof opts === 'object' ? Object.assign({indent: ' '}, opts) : {indent: opts || ' '}; + count = count === undefined ? 1 : count; + + if (typeof str !== 'string') { + throw new TypeError(`Expected \`input\` to be a \`string\`, got \`${typeof str}\``); + } + + if (typeof count !== 'number') { + throw new TypeError(`Expected \`count\` to be a \`number\`, got \`${typeof count}\``); + } + + if (typeof options.indent !== 'string') { + throw new TypeError(`Expected \`options.indent\` to be a \`string\`, got \`${typeof options.indent}\``); + } + + if (count === 0) { + return str; + } + + const regex = options.includeEmptyLines ? /^/mg : /^(?!\s*$)/mg; + return str.replace(regex, options.indent.repeat(count)); +} +; + + +/***/ }), +/* 260 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const os = __webpack_require__(11); + +const extractPathRegex = /\s+at.*(?:\(|\s)(.*)\)?/; +const pathRegex = /^(?:(?:(?:node|(?:internal\/[\w/]*|.*node_modules\/(?:babel-polyfill|pirates)\/.*)?\w+)\.js:\d+:\d+)|native)/; +const homeDir = os.homedir(); + +module.exports = (stack, options) => { + options = Object.assign({pretty: false}, options); + + return stack.replace(/\\/g, '/') + .split('\n') + .filter(line => { + const pathMatches = line.match(extractPathRegex); + if (pathMatches === null || !pathMatches[1]) { + return true; + } + + const match = pathMatches[1]; + + // Electron + if ( + match.includes('.app/Contents/Resources/electron.asar') || + match.includes('.app/Contents/Resources/default_app.asar') + ) { + return false; + } + + return !pathRegex.test(match); + }) + .filter(line => line.trim() !== '') + .map(line => { + if (options.pretty) { + return line.replace(extractPathRegex, (m, p1) => m.replace(p1, p1.replace(homeDir, '~'))); + } + + return line; + }) + .join('\n'); +}; + + +/***/ }), +/* 261 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const chalk = __webpack_require__(262); +const cliCursor = __webpack_require__(266); +const cliSpinners = __webpack_require__(270); +const logSymbols = __webpack_require__(158); class Ora { constructor(options) { @@ -25376,16 +32224,16 @@ module.exports.promise = (action, options) => { /***/ }), -/* 182 */ +/* 262 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const escapeStringRegexp = __webpack_require__(3); -const ansiStyles = __webpack_require__(183); -const stdoutColor = __webpack_require__(184).stdout; +const ansiStyles = __webpack_require__(263); +const stdoutColor = __webpack_require__(264).stdout; -const template = __webpack_require__(185); +const template = __webpack_require__(265); const isSimpleWindowsTerm = process.platform === 'win32' && !(process.env.TERM || '').toLowerCase().startsWith('xterm'); @@ -25611,7 +32459,7 @@ module.exports.default = module.exports; // For TypeScript /***/ }), -/* 183 */ +/* 263 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -25784,7 +32632,7 @@ Object.defineProperty(module, 'exports', { /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(5)(module))) /***/ }), -/* 184 */ +/* 264 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -25926,7 +32774,7 @@ module.exports = { /***/ }), -/* 185 */ +/* 265 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -26061,12 +32909,12 @@ module.exports = (chalk, tmp) => { /***/ }), -/* 186 */ +/* 266 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const restoreCursor = __webpack_require__(187); +const restoreCursor = __webpack_require__(267); let hidden = false; @@ -26107,12 +32955,12 @@ exports.toggle = (force, stream) => { /***/ }), -/* 187 */ +/* 267 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const onetime = __webpack_require__(188); +const onetime = __webpack_require__(268); const signalExit = __webpack_require__(110); module.exports = onetime(() => { @@ -26123,12 +32971,12 @@ module.exports = onetime(() => { /***/ }), -/* 188 */ +/* 268 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const mimicFn = __webpack_require__(189); +const mimicFn = __webpack_require__(269); module.exports = (fn, opts) => { // TODO: Remove this in v3 @@ -26169,7 +33017,7 @@ module.exports = (fn, opts) => { /***/ }), -/* 189 */ +/* 269 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -26185,22 +33033,22 @@ module.exports = (to, from) => { /***/ }), -/* 190 */ +/* 270 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -module.exports = __webpack_require__(191); +module.exports = __webpack_require__(271); /***/ }), -/* 191 */ +/* 271 */ /***/ (function(module) { module.exports = JSON.parse("{\"dots\":{\"interval\":80,\"frames\":[\"⠋\",\"⠙\",\"⠹\",\"⠸\",\"⠼\",\"⠴\",\"⠦\",\"⠧\",\"⠇\",\"⠏\"]},\"dots2\":{\"interval\":80,\"frames\":[\"⣾\",\"⣽\",\"⣻\",\"⢿\",\"⡿\",\"⣟\",\"⣯\",\"⣷\"]},\"dots3\":{\"interval\":80,\"frames\":[\"⠋\",\"⠙\",\"⠚\",\"⠞\",\"⠖\",\"⠦\",\"⠴\",\"⠲\",\"⠳\",\"⠓\"]},\"dots4\":{\"interval\":80,\"frames\":[\"⠄\",\"⠆\",\"⠇\",\"⠋\",\"⠙\",\"⠸\",\"⠰\",\"⠠\",\"⠰\",\"⠸\",\"⠙\",\"⠋\",\"⠇\",\"⠆\"]},\"dots5\":{\"interval\":80,\"frames\":[\"⠋\",\"⠙\",\"⠚\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠲\",\"⠴\",\"⠦\",\"⠖\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠓\",\"⠋\"]},\"dots6\":{\"interval\":80,\"frames\":[\"⠁\",\"⠉\",\"⠙\",\"⠚\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠲\",\"⠴\",\"⠤\",\"⠄\",\"⠄\",\"⠤\",\"⠴\",\"⠲\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠚\",\"⠙\",\"⠉\",\"⠁\"]},\"dots7\":{\"interval\":80,\"frames\":[\"⠈\",\"⠉\",\"⠋\",\"⠓\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠖\",\"⠦\",\"⠤\",\"⠠\",\"⠠\",\"⠤\",\"⠦\",\"⠖\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠓\",\"⠋\",\"⠉\",\"⠈\"]},\"dots8\":{\"interval\":80,\"frames\":[\"⠁\",\"⠁\",\"⠉\",\"⠙\",\"⠚\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠲\",\"⠴\",\"⠤\",\"⠄\",\"⠄\",\"⠤\",\"⠠\",\"⠠\",\"⠤\",\"⠦\",\"⠖\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠓\",\"⠋\",\"⠉\",\"⠈\",\"⠈\"]},\"dots9\":{\"interval\":80,\"frames\":[\"⢹\",\"⢺\",\"⢼\",\"⣸\",\"⣇\",\"⡧\",\"⡗\",\"⡏\"]},\"dots10\":{\"interval\":80,\"frames\":[\"⢄\",\"⢂\",\"⢁\",\"⡁\",\"⡈\",\"⡐\",\"⡠\"]},\"dots11\":{\"interval\":100,\"frames\":[\"⠁\",\"⠂\",\"⠄\",\"⡀\",\"⢀\",\"⠠\",\"⠐\",\"⠈\"]},\"dots12\":{\"interval\":80,\"frames\":[\"⢀⠀\",\"⡀⠀\",\"⠄⠀\",\"⢂⠀\",\"⡂⠀\",\"⠅⠀\",\"⢃⠀\",\"⡃⠀\",\"⠍⠀\",\"⢋⠀\",\"⡋⠀\",\"⠍⠁\",\"⢋⠁\",\"⡋⠁\",\"⠍⠉\",\"⠋⠉\",\"⠋⠉\",\"⠉⠙\",\"⠉⠙\",\"⠉⠩\",\"⠈⢙\",\"⠈⡙\",\"⢈⠩\",\"⡀⢙\",\"⠄⡙\",\"⢂⠩\",\"⡂⢘\",\"⠅⡘\",\"⢃⠨\",\"⡃⢐\",\"⠍⡐\",\"⢋⠠\",\"⡋⢀\",\"⠍⡁\",\"⢋⠁\",\"⡋⠁\",\"⠍⠉\",\"⠋⠉\",\"⠋⠉\",\"⠉⠙\",\"⠉⠙\",\"⠉⠩\",\"⠈⢙\",\"⠈⡙\",\"⠈⠩\",\"⠀⢙\",\"⠀⡙\",\"⠀⠩\",\"⠀⢘\",\"⠀⡘\",\"⠀⠨\",\"⠀⢐\",\"⠀⡐\",\"⠀⠠\",\"⠀⢀\",\"⠀⡀\"]},\"line\":{\"interval\":130,\"frames\":[\"-\",\"\\\\\",\"|\",\"/\"]},\"line2\":{\"interval\":100,\"frames\":[\"⠂\",\"-\",\"–\",\"—\",\"–\",\"-\"]},\"pipe\":{\"interval\":100,\"frames\":[\"┤\",\"┘\",\"┴\",\"└\",\"├\",\"┌\",\"┬\",\"┐\"]},\"simpleDots\":{\"interval\":400,\"frames\":[\". \",\".. \",\"...\",\" \"]},\"simpleDotsScrolling\":{\"interval\":200,\"frames\":[\". \",\".. \",\"...\",\" ..\",\" .\",\" \"]},\"star\":{\"interval\":70,\"frames\":[\"✶\",\"✸\",\"✹\",\"✺\",\"✹\",\"✷\"]},\"star2\":{\"interval\":80,\"frames\":[\"+\",\"x\",\"*\"]},\"flip\":{\"interval\":70,\"frames\":[\"_\",\"_\",\"_\",\"-\",\"`\",\"`\",\"'\",\"´\",\"-\",\"_\",\"_\",\"_\"]},\"hamburger\":{\"interval\":100,\"frames\":[\"☱\",\"☲\",\"☴\"]},\"growVertical\":{\"interval\":120,\"frames\":[\"▁\",\"▃\",\"▄\",\"▅\",\"▆\",\"▇\",\"▆\",\"▅\",\"▄\",\"▃\"]},\"growHorizontal\":{\"interval\":120,\"frames\":[\"▏\",\"▎\",\"▍\",\"▌\",\"▋\",\"▊\",\"▉\",\"▊\",\"▋\",\"▌\",\"▍\",\"▎\"]},\"balloon\":{\"interval\":140,\"frames\":[\" \",\".\",\"o\",\"O\",\"@\",\"*\",\" \"]},\"balloon2\":{\"interval\":120,\"frames\":[\".\",\"o\",\"O\",\"°\",\"O\",\"o\",\".\"]},\"noise\":{\"interval\":100,\"frames\":[\"▓\",\"▒\",\"░\"]},\"bounce\":{\"interval\":120,\"frames\":[\"⠁\",\"⠂\",\"⠄\",\"⠂\"]},\"boxBounce\":{\"interval\":120,\"frames\":[\"▖\",\"▘\",\"▝\",\"▗\"]},\"boxBounce2\":{\"interval\":100,\"frames\":[\"▌\",\"▀\",\"▐\",\"▄\"]},\"triangle\":{\"interval\":50,\"frames\":[\"◢\",\"◣\",\"◤\",\"◥\"]},\"arc\":{\"interval\":100,\"frames\":[\"◜\",\"◠\",\"◝\",\"◞\",\"◡\",\"◟\"]},\"circle\":{\"interval\":120,\"frames\":[\"◡\",\"⊙\",\"◠\"]},\"squareCorners\":{\"interval\":180,\"frames\":[\"◰\",\"◳\",\"◲\",\"◱\"]},\"circleQuarters\":{\"interval\":120,\"frames\":[\"◴\",\"◷\",\"◶\",\"◵\"]},\"circleHalves\":{\"interval\":50,\"frames\":[\"◐\",\"◓\",\"◑\",\"◒\"]},\"squish\":{\"interval\":100,\"frames\":[\"╫\",\"╪\"]},\"toggle\":{\"interval\":250,\"frames\":[\"⊶\",\"⊷\"]},\"toggle2\":{\"interval\":80,\"frames\":[\"▫\",\"▪\"]},\"toggle3\":{\"interval\":120,\"frames\":[\"□\",\"■\"]},\"toggle4\":{\"interval\":100,\"frames\":[\"■\",\"□\",\"▪\",\"▫\"]},\"toggle5\":{\"interval\":100,\"frames\":[\"▮\",\"▯\"]},\"toggle6\":{\"interval\":300,\"frames\":[\"ဝ\",\"၀\"]},\"toggle7\":{\"interval\":80,\"frames\":[\"⦾\",\"⦿\"]},\"toggle8\":{\"interval\":100,\"frames\":[\"◍\",\"◌\"]},\"toggle9\":{\"interval\":100,\"frames\":[\"◉\",\"◎\"]},\"toggle10\":{\"interval\":100,\"frames\":[\"㊂\",\"㊀\",\"㊁\"]},\"toggle11\":{\"interval\":50,\"frames\":[\"⧇\",\"⧆\"]},\"toggle12\":{\"interval\":120,\"frames\":[\"☗\",\"☖\"]},\"toggle13\":{\"interval\":80,\"frames\":[\"=\",\"*\",\"-\"]},\"arrow\":{\"interval\":100,\"frames\":[\"←\",\"↖\",\"↑\",\"↗\",\"→\",\"↘\",\"↓\",\"↙\"]},\"arrow2\":{\"interval\":80,\"frames\":[\"⬆️ \",\"↗️ \",\"➡️ \",\"↘️ \",\"⬇️ \",\"↙️ \",\"⬅️ \",\"↖️ \"]},\"arrow3\":{\"interval\":120,\"frames\":[\"▹▹▹▹▹\",\"▸▹▹▹▹\",\"▹▸▹▹▹\",\"▹▹▸▹▹\",\"▹▹▹▸▹\",\"▹▹▹▹▸\"]},\"bouncingBar\":{\"interval\":80,\"frames\":[\"[ ]\",\"[= ]\",\"[== ]\",\"[=== ]\",\"[ ===]\",\"[ ==]\",\"[ =]\",\"[ ]\",\"[ =]\",\"[ ==]\",\"[ ===]\",\"[====]\",\"[=== ]\",\"[== ]\",\"[= ]\"]},\"bouncingBall\":{\"interval\":80,\"frames\":[\"( ● )\",\"( ● )\",\"( ● )\",\"( ● )\",\"( ●)\",\"( ● )\",\"( ● )\",\"( ● )\",\"( ● )\",\"(● )\"]},\"smiley\":{\"interval\":200,\"frames\":[\"😄 \",\"😝 \"]},\"monkey\":{\"interval\":300,\"frames\":[\"🙈 \",\"🙈 \",\"🙉 \",\"🙊 \"]},\"hearts\":{\"interval\":100,\"frames\":[\"💛 \",\"💙 \",\"💜 \",\"💚 \",\"❤️ \"]},\"clock\":{\"interval\":100,\"frames\":[\"🕐 \",\"🕑 \",\"🕒 \",\"🕓 \",\"🕔 \",\"🕕 \",\"🕖 \",\"🕗 \",\"🕘 \",\"🕙 \",\"🕚 \"]},\"earth\":{\"interval\":180,\"frames\":[\"🌍 \",\"🌎 \",\"🌏 \"]},\"moon\":{\"interval\":80,\"frames\":[\"🌑 \",\"🌒 \",\"🌓 \",\"🌔 \",\"🌕 \",\"🌖 \",\"🌗 \",\"🌘 \"]},\"runner\":{\"interval\":140,\"frames\":[\"🚶 \",\"🏃 \"]},\"pong\":{\"interval\":80,\"frames\":[\"▐⠂ ▌\",\"▐⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂▌\",\"▐ ⠠▌\",\"▐ ⡀▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐⠠ ▌\"]},\"shark\":{\"interval\":120,\"frames\":[\"▐|\\\\____________▌\",\"▐_|\\\\___________▌\",\"▐__|\\\\__________▌\",\"▐___|\\\\_________▌\",\"▐____|\\\\________▌\",\"▐_____|\\\\_______▌\",\"▐______|\\\\______▌\",\"▐_______|\\\\_____▌\",\"▐________|\\\\____▌\",\"▐_________|\\\\___▌\",\"▐__________|\\\\__▌\",\"▐___________|\\\\_▌\",\"▐____________|\\\\▌\",\"▐____________/|▌\",\"▐___________/|_▌\",\"▐__________/|__▌\",\"▐_________/|___▌\",\"▐________/|____▌\",\"▐_______/|_____▌\",\"▐______/|______▌\",\"▐_____/|_______▌\",\"▐____/|________▌\",\"▐___/|_________▌\",\"▐__/|__________▌\",\"▐_/|___________▌\",\"▐/|____________▌\"]},\"dqpb\":{\"interval\":100,\"frames\":[\"d\",\"q\",\"p\",\"b\"]},\"weather\":{\"interval\":100,\"frames\":[\"☀️ \",\"☀️ \",\"☀️ \",\"🌤 \",\"⛅️ \",\"🌥 \",\"☁️ \",\"🌧 \",\"🌨 \",\"🌧 \",\"🌨 \",\"🌧 \",\"🌨 \",\"⛈ \",\"🌨 \",\"🌧 \",\"🌨 \",\"☁️ \",\"🌥 \",\"⛅️ \",\"🌤 \",\"☀️ \",\"☀️ \"]},\"christmas\":{\"interval\":400,\"frames\":[\"🌲\",\"🎄\"]}}"); /***/ }), -/* 192 */ +/* 272 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -26260,7 +33108,7 @@ const RunCommand = { }; /***/ }), -/* 193 */ +/* 273 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -26271,7 +33119,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(34); /* harmony import */ var _utils_parallelize__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(35); /* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(36); -/* harmony import */ var _utils_watch__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(194); +/* harmony import */ var _utils_watch__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(274); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -26355,14 +33203,14 @@ const WatchCommand = { }; /***/ }), -/* 194 */ +/* 274 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "waitUntilWatchIsReady", function() { return waitUntilWatchIsReady; }); -/* harmony import */ var rxjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(195); -/* harmony import */ var rxjs_operators__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(294); +/* harmony import */ var rxjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(275); +/* harmony import */ var rxjs_operators__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(374); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -26429,168 +33277,168 @@ function waitUntilWatchIsReady(stream, opts = {}) { } /***/ }), -/* 195 */ +/* 275 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); -/* harmony import */ var _internal_Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); +/* harmony import */ var _internal_Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "Observable", function() { return _internal_Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"]; }); -/* harmony import */ var _internal_observable_ConnectableObservable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(214); +/* harmony import */ var _internal_observable_ConnectableObservable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(294); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "ConnectableObservable", function() { return _internal_observable_ConnectableObservable__WEBPACK_IMPORTED_MODULE_1__["ConnectableObservable"]; }); -/* harmony import */ var _internal_operators_groupBy__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(219); +/* harmony import */ var _internal_operators_groupBy__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(299); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "GroupedObservable", function() { return _internal_operators_groupBy__WEBPACK_IMPORTED_MODULE_2__["GroupedObservable"]; }); -/* harmony import */ var _internal_symbol_observable__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(211); +/* harmony import */ var _internal_symbol_observable__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(291); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "observable", function() { return _internal_symbol_observable__WEBPACK_IMPORTED_MODULE_3__["observable"]; }); -/* harmony import */ var _internal_Subject__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(215); +/* harmony import */ var _internal_Subject__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(295); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "Subject", function() { return _internal_Subject__WEBPACK_IMPORTED_MODULE_4__["Subject"]; }); -/* harmony import */ var _internal_BehaviorSubject__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(220); +/* harmony import */ var _internal_BehaviorSubject__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(300); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "BehaviorSubject", function() { return _internal_BehaviorSubject__WEBPACK_IMPORTED_MODULE_5__["BehaviorSubject"]; }); -/* harmony import */ var _internal_ReplaySubject__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(221); +/* harmony import */ var _internal_ReplaySubject__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(301); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "ReplaySubject", function() { return _internal_ReplaySubject__WEBPACK_IMPORTED_MODULE_6__["ReplaySubject"]; }); -/* harmony import */ var _internal_AsyncSubject__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(238); +/* harmony import */ var _internal_AsyncSubject__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(318); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "AsyncSubject", function() { return _internal_AsyncSubject__WEBPACK_IMPORTED_MODULE_7__["AsyncSubject"]; }); -/* harmony import */ var _internal_scheduler_asap__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(239); +/* harmony import */ var _internal_scheduler_asap__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(319); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "asapScheduler", function() { return _internal_scheduler_asap__WEBPACK_IMPORTED_MODULE_8__["asap"]; }); -/* harmony import */ var _internal_scheduler_async__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(243); +/* harmony import */ var _internal_scheduler_async__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(323); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "asyncScheduler", function() { return _internal_scheduler_async__WEBPACK_IMPORTED_MODULE_9__["async"]; }); -/* harmony import */ var _internal_scheduler_queue__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(222); +/* harmony import */ var _internal_scheduler_queue__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(302); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "queueScheduler", function() { return _internal_scheduler_queue__WEBPACK_IMPORTED_MODULE_10__["queue"]; }); -/* harmony import */ var _internal_scheduler_animationFrame__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(244); +/* harmony import */ var _internal_scheduler_animationFrame__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(324); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "animationFrameScheduler", function() { return _internal_scheduler_animationFrame__WEBPACK_IMPORTED_MODULE_11__["animationFrame"]; }); -/* harmony import */ var _internal_scheduler_VirtualTimeScheduler__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(247); +/* harmony import */ var _internal_scheduler_VirtualTimeScheduler__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(327); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "VirtualTimeScheduler", function() { return _internal_scheduler_VirtualTimeScheduler__WEBPACK_IMPORTED_MODULE_12__["VirtualTimeScheduler"]; }); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "VirtualAction", function() { return _internal_scheduler_VirtualTimeScheduler__WEBPACK_IMPORTED_MODULE_12__["VirtualAction"]; }); -/* harmony import */ var _internal_Scheduler__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(228); +/* harmony import */ var _internal_Scheduler__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(308); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "Scheduler", function() { return _internal_Scheduler__WEBPACK_IMPORTED_MODULE_13__["Scheduler"]; }); -/* harmony import */ var _internal_Subscription__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(204); +/* harmony import */ var _internal_Subscription__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(284); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "Subscription", function() { return _internal_Subscription__WEBPACK_IMPORTED_MODULE_14__["Subscription"]; }); -/* harmony import */ var _internal_Subscriber__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(198); +/* harmony import */ var _internal_Subscriber__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(278); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "Subscriber", function() { return _internal_Subscriber__WEBPACK_IMPORTED_MODULE_15__["Subscriber"]; }); -/* harmony import */ var _internal_Notification__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(230); +/* harmony import */ var _internal_Notification__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(310); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "Notification", function() { return _internal_Notification__WEBPACK_IMPORTED_MODULE_16__["Notification"]; }); -/* harmony import */ var _internal_util_pipe__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(212); +/* harmony import */ var _internal_util_pipe__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(292); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "pipe", function() { return _internal_util_pipe__WEBPACK_IMPORTED_MODULE_17__["pipe"]; }); -/* harmony import */ var _internal_util_noop__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(213); +/* harmony import */ var _internal_util_noop__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(293); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "noop", function() { return _internal_util_noop__WEBPACK_IMPORTED_MODULE_18__["noop"]; }); -/* harmony import */ var _internal_util_identity__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(248); +/* harmony import */ var _internal_util_identity__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(328); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "identity", function() { return _internal_util_identity__WEBPACK_IMPORTED_MODULE_19__["identity"]; }); -/* harmony import */ var _internal_util_isObservable__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(249); +/* harmony import */ var _internal_util_isObservable__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(329); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "isObservable", function() { return _internal_util_isObservable__WEBPACK_IMPORTED_MODULE_20__["isObservable"]; }); -/* harmony import */ var _internal_util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__(250); +/* harmony import */ var _internal_util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__(330); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "ArgumentOutOfRangeError", function() { return _internal_util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_21__["ArgumentOutOfRangeError"]; }); -/* harmony import */ var _internal_util_EmptyError__WEBPACK_IMPORTED_MODULE_22__ = __webpack_require__(251); +/* harmony import */ var _internal_util_EmptyError__WEBPACK_IMPORTED_MODULE_22__ = __webpack_require__(331); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "EmptyError", function() { return _internal_util_EmptyError__WEBPACK_IMPORTED_MODULE_22__["EmptyError"]; }); -/* harmony import */ var _internal_util_ObjectUnsubscribedError__WEBPACK_IMPORTED_MODULE_23__ = __webpack_require__(216); +/* harmony import */ var _internal_util_ObjectUnsubscribedError__WEBPACK_IMPORTED_MODULE_23__ = __webpack_require__(296); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "ObjectUnsubscribedError", function() { return _internal_util_ObjectUnsubscribedError__WEBPACK_IMPORTED_MODULE_23__["ObjectUnsubscribedError"]; }); -/* harmony import */ var _internal_util_UnsubscriptionError__WEBPACK_IMPORTED_MODULE_24__ = __webpack_require__(209); +/* harmony import */ var _internal_util_UnsubscriptionError__WEBPACK_IMPORTED_MODULE_24__ = __webpack_require__(289); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "UnsubscriptionError", function() { return _internal_util_UnsubscriptionError__WEBPACK_IMPORTED_MODULE_24__["UnsubscriptionError"]; }); -/* harmony import */ var _internal_util_TimeoutError__WEBPACK_IMPORTED_MODULE_25__ = __webpack_require__(252); +/* harmony import */ var _internal_util_TimeoutError__WEBPACK_IMPORTED_MODULE_25__ = __webpack_require__(332); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "TimeoutError", function() { return _internal_util_TimeoutError__WEBPACK_IMPORTED_MODULE_25__["TimeoutError"]; }); -/* harmony import */ var _internal_observable_bindCallback__WEBPACK_IMPORTED_MODULE_26__ = __webpack_require__(253); +/* harmony import */ var _internal_observable_bindCallback__WEBPACK_IMPORTED_MODULE_26__ = __webpack_require__(333); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bindCallback", function() { return _internal_observable_bindCallback__WEBPACK_IMPORTED_MODULE_26__["bindCallback"]; }); -/* harmony import */ var _internal_observable_bindNodeCallback__WEBPACK_IMPORTED_MODULE_27__ = __webpack_require__(255); +/* harmony import */ var _internal_observable_bindNodeCallback__WEBPACK_IMPORTED_MODULE_27__ = __webpack_require__(335); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bindNodeCallback", function() { return _internal_observable_bindNodeCallback__WEBPACK_IMPORTED_MODULE_27__["bindNodeCallback"]; }); -/* harmony import */ var _internal_observable_combineLatest__WEBPACK_IMPORTED_MODULE_28__ = __webpack_require__(256); +/* harmony import */ var _internal_observable_combineLatest__WEBPACK_IMPORTED_MODULE_28__ = __webpack_require__(336); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "combineLatest", function() { return _internal_observable_combineLatest__WEBPACK_IMPORTED_MODULE_28__["combineLatest"]; }); -/* harmony import */ var _internal_observable_concat__WEBPACK_IMPORTED_MODULE_29__ = __webpack_require__(267); +/* harmony import */ var _internal_observable_concat__WEBPACK_IMPORTED_MODULE_29__ = __webpack_require__(347); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concat", function() { return _internal_observable_concat__WEBPACK_IMPORTED_MODULE_29__["concat"]; }); -/* harmony import */ var _internal_observable_defer__WEBPACK_IMPORTED_MODULE_30__ = __webpack_require__(277); +/* harmony import */ var _internal_observable_defer__WEBPACK_IMPORTED_MODULE_30__ = __webpack_require__(357); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "defer", function() { return _internal_observable_defer__WEBPACK_IMPORTED_MODULE_30__["defer"]; }); -/* harmony import */ var _internal_observable_empty__WEBPACK_IMPORTED_MODULE_31__ = __webpack_require__(231); +/* harmony import */ var _internal_observable_empty__WEBPACK_IMPORTED_MODULE_31__ = __webpack_require__(311); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "empty", function() { return _internal_observable_empty__WEBPACK_IMPORTED_MODULE_31__["empty"]; }); -/* harmony import */ var _internal_observable_forkJoin__WEBPACK_IMPORTED_MODULE_32__ = __webpack_require__(278); +/* harmony import */ var _internal_observable_forkJoin__WEBPACK_IMPORTED_MODULE_32__ = __webpack_require__(358); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "forkJoin", function() { return _internal_observable_forkJoin__WEBPACK_IMPORTED_MODULE_32__["forkJoin"]; }); -/* harmony import */ var _internal_observable_from__WEBPACK_IMPORTED_MODULE_33__ = __webpack_require__(268); +/* harmony import */ var _internal_observable_from__WEBPACK_IMPORTED_MODULE_33__ = __webpack_require__(348); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "from", function() { return _internal_observable_from__WEBPACK_IMPORTED_MODULE_33__["from"]; }); -/* harmony import */ var _internal_observable_fromEvent__WEBPACK_IMPORTED_MODULE_34__ = __webpack_require__(279); +/* harmony import */ var _internal_observable_fromEvent__WEBPACK_IMPORTED_MODULE_34__ = __webpack_require__(359); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "fromEvent", function() { return _internal_observable_fromEvent__WEBPACK_IMPORTED_MODULE_34__["fromEvent"]; }); -/* harmony import */ var _internal_observable_fromEventPattern__WEBPACK_IMPORTED_MODULE_35__ = __webpack_require__(280); +/* harmony import */ var _internal_observable_fromEventPattern__WEBPACK_IMPORTED_MODULE_35__ = __webpack_require__(360); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "fromEventPattern", function() { return _internal_observable_fromEventPattern__WEBPACK_IMPORTED_MODULE_35__["fromEventPattern"]; }); -/* harmony import */ var _internal_observable_generate__WEBPACK_IMPORTED_MODULE_36__ = __webpack_require__(281); +/* harmony import */ var _internal_observable_generate__WEBPACK_IMPORTED_MODULE_36__ = __webpack_require__(361); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "generate", function() { return _internal_observable_generate__WEBPACK_IMPORTED_MODULE_36__["generate"]; }); -/* harmony import */ var _internal_observable_iif__WEBPACK_IMPORTED_MODULE_37__ = __webpack_require__(282); +/* harmony import */ var _internal_observable_iif__WEBPACK_IMPORTED_MODULE_37__ = __webpack_require__(362); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "iif", function() { return _internal_observable_iif__WEBPACK_IMPORTED_MODULE_37__["iif"]; }); -/* harmony import */ var _internal_observable_interval__WEBPACK_IMPORTED_MODULE_38__ = __webpack_require__(283); +/* harmony import */ var _internal_observable_interval__WEBPACK_IMPORTED_MODULE_38__ = __webpack_require__(363); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "interval", function() { return _internal_observable_interval__WEBPACK_IMPORTED_MODULE_38__["interval"]; }); -/* harmony import */ var _internal_observable_merge__WEBPACK_IMPORTED_MODULE_39__ = __webpack_require__(285); +/* harmony import */ var _internal_observable_merge__WEBPACK_IMPORTED_MODULE_39__ = __webpack_require__(365); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "merge", function() { return _internal_observable_merge__WEBPACK_IMPORTED_MODULE_39__["merge"]; }); -/* harmony import */ var _internal_observable_never__WEBPACK_IMPORTED_MODULE_40__ = __webpack_require__(286); +/* harmony import */ var _internal_observable_never__WEBPACK_IMPORTED_MODULE_40__ = __webpack_require__(366); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "never", function() { return _internal_observable_never__WEBPACK_IMPORTED_MODULE_40__["never"]; }); -/* harmony import */ var _internal_observable_of__WEBPACK_IMPORTED_MODULE_41__ = __webpack_require__(232); +/* harmony import */ var _internal_observable_of__WEBPACK_IMPORTED_MODULE_41__ = __webpack_require__(312); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "of", function() { return _internal_observable_of__WEBPACK_IMPORTED_MODULE_41__["of"]; }); -/* harmony import */ var _internal_observable_onErrorResumeNext__WEBPACK_IMPORTED_MODULE_42__ = __webpack_require__(287); +/* harmony import */ var _internal_observable_onErrorResumeNext__WEBPACK_IMPORTED_MODULE_42__ = __webpack_require__(367); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "onErrorResumeNext", function() { return _internal_observable_onErrorResumeNext__WEBPACK_IMPORTED_MODULE_42__["onErrorResumeNext"]; }); -/* harmony import */ var _internal_observable_pairs__WEBPACK_IMPORTED_MODULE_43__ = __webpack_require__(288); +/* harmony import */ var _internal_observable_pairs__WEBPACK_IMPORTED_MODULE_43__ = __webpack_require__(368); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "pairs", function() { return _internal_observable_pairs__WEBPACK_IMPORTED_MODULE_43__["pairs"]; }); -/* harmony import */ var _internal_observable_race__WEBPACK_IMPORTED_MODULE_44__ = __webpack_require__(289); +/* harmony import */ var _internal_observable_race__WEBPACK_IMPORTED_MODULE_44__ = __webpack_require__(369); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "race", function() { return _internal_observable_race__WEBPACK_IMPORTED_MODULE_44__["race"]; }); -/* harmony import */ var _internal_observable_range__WEBPACK_IMPORTED_MODULE_45__ = __webpack_require__(290); +/* harmony import */ var _internal_observable_range__WEBPACK_IMPORTED_MODULE_45__ = __webpack_require__(370); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "range", function() { return _internal_observable_range__WEBPACK_IMPORTED_MODULE_45__["range"]; }); -/* harmony import */ var _internal_observable_throwError__WEBPACK_IMPORTED_MODULE_46__ = __webpack_require__(237); +/* harmony import */ var _internal_observable_throwError__WEBPACK_IMPORTED_MODULE_46__ = __webpack_require__(317); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "throwError", function() { return _internal_observable_throwError__WEBPACK_IMPORTED_MODULE_46__["throwError"]; }); -/* harmony import */ var _internal_observable_timer__WEBPACK_IMPORTED_MODULE_47__ = __webpack_require__(291); +/* harmony import */ var _internal_observable_timer__WEBPACK_IMPORTED_MODULE_47__ = __webpack_require__(371); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "timer", function() { return _internal_observable_timer__WEBPACK_IMPORTED_MODULE_47__["timer"]; }); -/* harmony import */ var _internal_observable_using__WEBPACK_IMPORTED_MODULE_48__ = __webpack_require__(292); +/* harmony import */ var _internal_observable_using__WEBPACK_IMPORTED_MODULE_48__ = __webpack_require__(372); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "using", function() { return _internal_observable_using__WEBPACK_IMPORTED_MODULE_48__["using"]; }); -/* harmony import */ var _internal_observable_zip__WEBPACK_IMPORTED_MODULE_49__ = __webpack_require__(293); +/* harmony import */ var _internal_observable_zip__WEBPACK_IMPORTED_MODULE_49__ = __webpack_require__(373); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "zip", function() { return _internal_observable_zip__WEBPACK_IMPORTED_MODULE_49__["zip"]; }); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "EMPTY", function() { return _internal_observable_empty__WEBPACK_IMPORTED_MODULE_31__["EMPTY"]; }); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "NEVER", function() { return _internal_observable_never__WEBPACK_IMPORTED_MODULE_40__["NEVER"]; }); -/* harmony import */ var _internal_config__WEBPACK_IMPORTED_MODULE_50__ = __webpack_require__(202); +/* harmony import */ var _internal_config__WEBPACK_IMPORTED_MODULE_50__ = __webpack_require__(282); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "config", function() { return _internal_config__WEBPACK_IMPORTED_MODULE_50__["config"]; }); /** PURE_IMPORTS_START PURE_IMPORTS_END */ @@ -26651,16 +33499,16 @@ __webpack_require__.r(__webpack_exports__); /***/ }), -/* 196 */ +/* 276 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Observable", function() { return Observable; }); -/* harmony import */ var _util_toSubscriber__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(197); -/* harmony import */ var _internal_symbol_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(211); -/* harmony import */ var _util_pipe__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(212); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(202); +/* harmony import */ var _util_toSubscriber__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(277); +/* harmony import */ var _internal_symbol_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(291); +/* harmony import */ var _util_pipe__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(292); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(282); /** PURE_IMPORTS_START _util_toSubscriber,_internal_symbol_observable,_util_pipe,_config PURE_IMPORTS_END */ @@ -26774,15 +33622,15 @@ function getPromiseCtor(promiseCtor) { /***/ }), -/* 197 */ +/* 277 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "toSubscriber", function() { return toSubscriber; }); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(198); -/* harmony import */ var _symbol_rxSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(210); -/* harmony import */ var _Observer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(201); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(278); +/* harmony import */ var _symbol_rxSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(290); +/* harmony import */ var _Observer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(281); /** PURE_IMPORTS_START _Subscriber,_symbol_rxSubscriber,_Observer PURE_IMPORTS_END */ @@ -26805,19 +33653,19 @@ function toSubscriber(nextOrObserver, error, complete) { /***/ }), -/* 198 */ +/* 278 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Subscriber", function() { return Subscriber; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _util_isFunction__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(200); -/* harmony import */ var _Observer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(201); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(204); -/* harmony import */ var _internal_symbol_rxSubscriber__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(210); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(202); -/* harmony import */ var _util_hostReportError__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(203); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _util_isFunction__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(280); +/* harmony import */ var _Observer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(281); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(284); +/* harmony import */ var _internal_symbol_rxSubscriber__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(290); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(282); +/* harmony import */ var _util_hostReportError__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(283); /** PURE_IMPORTS_START tslib,_util_isFunction,_Observer,_Subscription,_internal_symbol_rxSubscriber,_config,_util_hostReportError PURE_IMPORTS_END */ @@ -27059,7 +33907,7 @@ function isTrustedSubscriber(obj) { /***/ }), -/* 199 */ +/* 279 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -27272,7 +34120,7 @@ function __importDefault(mod) { /***/ }), -/* 200 */ +/* 280 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -27286,14 +34134,14 @@ function isFunction(x) { /***/ }), -/* 201 */ +/* 281 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "empty", function() { return empty; }); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(202); -/* harmony import */ var _util_hostReportError__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(203); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(282); +/* harmony import */ var _util_hostReportError__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(283); /** PURE_IMPORTS_START _config,_util_hostReportError PURE_IMPORTS_END */ @@ -27314,7 +34162,7 @@ var empty = { /***/ }), -/* 202 */ +/* 282 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -27342,7 +34190,7 @@ var config = { /***/ }), -/* 203 */ +/* 283 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -27356,18 +34204,18 @@ function hostReportError(err) { /***/ }), -/* 204 */ +/* 284 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Subscription", function() { return Subscription; }); -/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(205); -/* harmony import */ var _util_isObject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(206); -/* harmony import */ var _util_isFunction__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(200); -/* harmony import */ var _util_tryCatch__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(207); -/* harmony import */ var _util_errorObject__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(208); -/* harmony import */ var _util_UnsubscriptionError__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(209); +/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(285); +/* harmony import */ var _util_isObject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(286); +/* harmony import */ var _util_isFunction__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(280); +/* harmony import */ var _util_tryCatch__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(287); +/* harmony import */ var _util_errorObject__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(288); +/* harmony import */ var _util_UnsubscriptionError__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(289); /** PURE_IMPORTS_START _util_isArray,_util_isObject,_util_isFunction,_util_tryCatch,_util_errorObject,_util_UnsubscriptionError PURE_IMPORTS_END */ @@ -27503,7 +34351,7 @@ function flattenUnsubscriptionErrors(errors) { /***/ }), -/* 205 */ +/* 285 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -27515,7 +34363,7 @@ var isArray = Array.isArray || (function (x) { return x && typeof x.length === ' /***/ }), -/* 206 */ +/* 286 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -27529,13 +34377,13 @@ function isObject(x) { /***/ }), -/* 207 */ +/* 287 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "tryCatch", function() { return tryCatch; }); -/* harmony import */ var _errorObject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(208); +/* harmony import */ var _errorObject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(288); /** PURE_IMPORTS_START _errorObject PURE_IMPORTS_END */ var tryCatchTarget; @@ -27556,7 +34404,7 @@ function tryCatch(fn) { /***/ }), -/* 208 */ +/* 288 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -27568,13 +34416,13 @@ var errorObject = { e: {} }; /***/ }), -/* 209 */ +/* 289 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "UnsubscriptionError", function() { return UnsubscriptionError; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); /** PURE_IMPORTS_START tslib PURE_IMPORTS_END */ var UnsubscriptionError = /*@__PURE__*/ (function (_super) { @@ -27594,7 +34442,7 @@ var UnsubscriptionError = /*@__PURE__*/ (function (_super) { /***/ }), -/* 210 */ +/* 290 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -27610,7 +34458,7 @@ var $$rxSubscriber = rxSubscriber; /***/ }), -/* 211 */ +/* 291 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -27622,14 +34470,14 @@ var observable = typeof Symbol === 'function' && Symbol.observable || '@@observa /***/ }), -/* 212 */ +/* 292 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "pipe", function() { return pipe; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "pipeFromArray", function() { return pipeFromArray; }); -/* harmony import */ var _noop__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(213); +/* harmony import */ var _noop__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(293); /** PURE_IMPORTS_START _noop PURE_IMPORTS_END */ function pipe() { @@ -27654,7 +34502,7 @@ function pipeFromArray(fns) { /***/ }), -/* 213 */ +/* 293 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -27666,19 +34514,19 @@ function noop() { } /***/ }), -/* 214 */ +/* 294 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ConnectableObservable", function() { return ConnectableObservable; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "connectableObservableDescriptor", function() { return connectableObservableDescriptor; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(215); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(196); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(198); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(204); -/* harmony import */ var _operators_refCount__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(218); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(295); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(276); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(278); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(284); +/* harmony import */ var _operators_refCount__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(298); /** PURE_IMPORTS_START tslib,_Subject,_Observable,_Subscriber,_Subscription,_operators_refCount PURE_IMPORTS_END */ @@ -27825,7 +34673,7 @@ var RefCountSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 215 */ +/* 295 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -27833,13 +34681,13 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SubjectSubscriber", function() { return SubjectSubscriber; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Subject", function() { return Subject; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AnonymousSubject", function() { return AnonymousSubject; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(196); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(198); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(204); -/* harmony import */ var _util_ObjectUnsubscribedError__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(216); -/* harmony import */ var _SubjectSubscription__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(217); -/* harmony import */ var _internal_symbol_rxSubscriber__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(210); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(276); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(278); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(284); +/* harmony import */ var _util_ObjectUnsubscribedError__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(296); +/* harmony import */ var _SubjectSubscription__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(297); +/* harmony import */ var _internal_symbol_rxSubscriber__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(290); /** PURE_IMPORTS_START tslib,_Observable,_Subscriber,_Subscription,_util_ObjectUnsubscribedError,_SubjectSubscription,_internal_symbol_rxSubscriber PURE_IMPORTS_END */ @@ -28001,13 +34849,13 @@ var AnonymousSubject = /*@__PURE__*/ (function (_super) { /***/ }), -/* 216 */ +/* 296 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ObjectUnsubscribedError", function() { return ObjectUnsubscribedError; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); /** PURE_IMPORTS_START tslib PURE_IMPORTS_END */ var ObjectUnsubscribedError = /*@__PURE__*/ (function (_super) { @@ -28025,14 +34873,14 @@ var ObjectUnsubscribedError = /*@__PURE__*/ (function (_super) { /***/ }), -/* 217 */ +/* 297 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SubjectSubscription", function() { return SubjectSubscription; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(204); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(284); /** PURE_IMPORTS_START tslib,_Subscription PURE_IMPORTS_END */ @@ -28068,14 +34916,14 @@ var SubjectSubscription = /*@__PURE__*/ (function (_super) { /***/ }), -/* 218 */ +/* 298 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "refCount", function() { return refCount; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); /** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ @@ -28137,18 +34985,18 @@ var RefCountSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 219 */ +/* 299 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "groupBy", function() { return groupBy; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "GroupedObservable", function() { return GroupedObservable; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(204); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(196); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(215); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(284); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(276); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(295); /** PURE_IMPORTS_START tslib,_Subscriber,_Subscription,_Observable,_Subject PURE_IMPORTS_END */ @@ -28334,15 +35182,15 @@ var InnerRefCountSubscription = /*@__PURE__*/ (function (_super) { /***/ }), -/* 220 */ +/* 300 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "BehaviorSubject", function() { return BehaviorSubject; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(215); -/* harmony import */ var _util_ObjectUnsubscribedError__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(216); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(295); +/* harmony import */ var _util_ObjectUnsubscribedError__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(296); /** PURE_IMPORTS_START tslib,_Subject,_util_ObjectUnsubscribedError PURE_IMPORTS_END */ @@ -28389,19 +35237,19 @@ var BehaviorSubject = /*@__PURE__*/ (function (_super) { /***/ }), -/* 221 */ +/* 301 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ReplaySubject", function() { return ReplaySubject; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(215); -/* harmony import */ var _scheduler_queue__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(222); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(204); -/* harmony import */ var _operators_observeOn__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(229); -/* harmony import */ var _util_ObjectUnsubscribedError__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(216); -/* harmony import */ var _SubjectSubscription__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(217); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(295); +/* harmony import */ var _scheduler_queue__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(302); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(284); +/* harmony import */ var _operators_observeOn__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(309); +/* harmony import */ var _util_ObjectUnsubscribedError__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(296); +/* harmony import */ var _SubjectSubscription__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(297); /** PURE_IMPORTS_START tslib,_Subject,_scheduler_queue,_Subscription,_operators_observeOn,_util_ObjectUnsubscribedError,_SubjectSubscription PURE_IMPORTS_END */ @@ -28522,14 +35370,14 @@ var ReplayEvent = /*@__PURE__*/ (function () { /***/ }), -/* 222 */ +/* 302 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "queue", function() { return queue; }); -/* harmony import */ var _QueueAction__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(223); -/* harmony import */ var _QueueScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(226); +/* harmony import */ var _QueueAction__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(303); +/* harmony import */ var _QueueScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(306); /** PURE_IMPORTS_START _QueueAction,_QueueScheduler PURE_IMPORTS_END */ @@ -28538,14 +35386,14 @@ var queue = /*@__PURE__*/ new _QueueScheduler__WEBPACK_IMPORTED_MODULE_1__["Queu /***/ }), -/* 223 */ +/* 303 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "QueueAction", function() { return QueueAction; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _AsyncAction__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(224); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _AsyncAction__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(304); /** PURE_IMPORTS_START tslib,_AsyncAction PURE_IMPORTS_END */ @@ -28590,14 +35438,14 @@ var QueueAction = /*@__PURE__*/ (function (_super) { /***/ }), -/* 224 */ +/* 304 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AsyncAction", function() { return AsyncAction; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Action__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(225); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Action__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(305); /** PURE_IMPORTS_START tslib,_Action PURE_IMPORTS_END */ @@ -28695,14 +35543,14 @@ var AsyncAction = /*@__PURE__*/ (function (_super) { /***/ }), -/* 225 */ +/* 305 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Action", function() { return Action; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(204); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(284); /** PURE_IMPORTS_START tslib,_Subscription PURE_IMPORTS_END */ @@ -28724,14 +35572,14 @@ var Action = /*@__PURE__*/ (function (_super) { /***/ }), -/* 226 */ +/* 306 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "QueueScheduler", function() { return QueueScheduler; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _AsyncScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(227); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _AsyncScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(307); /** PURE_IMPORTS_START tslib,_AsyncScheduler PURE_IMPORTS_END */ @@ -28747,14 +35595,14 @@ var QueueScheduler = /*@__PURE__*/ (function (_super) { /***/ }), -/* 227 */ +/* 307 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AsyncScheduler", function() { return AsyncScheduler; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Scheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(228); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Scheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(308); /** PURE_IMPORTS_START tslib,_Scheduler PURE_IMPORTS_END */ @@ -28816,7 +35664,7 @@ var AsyncScheduler = /*@__PURE__*/ (function (_super) { /***/ }), -/* 228 */ +/* 308 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -28844,7 +35692,7 @@ var Scheduler = /*@__PURE__*/ (function () { /***/ }), -/* 229 */ +/* 309 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -28853,9 +35701,9 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ObserveOnOperator", function() { return ObserveOnOperator; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ObserveOnSubscriber", function() { return ObserveOnSubscriber; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ObserveOnMessage", function() { return ObserveOnMessage; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); -/* harmony import */ var _Notification__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(230); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); +/* harmony import */ var _Notification__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(310); /** PURE_IMPORTS_START tslib,_Subscriber,_Notification PURE_IMPORTS_END */ @@ -28925,15 +35773,15 @@ var ObserveOnMessage = /*@__PURE__*/ (function () { /***/ }), -/* 230 */ +/* 310 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Notification", function() { return Notification; }); -/* harmony import */ var _observable_empty__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(231); -/* harmony import */ var _observable_of__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(232); -/* harmony import */ var _observable_throwError__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(237); +/* harmony import */ var _observable_empty__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(311); +/* harmony import */ var _observable_of__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(312); +/* harmony import */ var _observable_throwError__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(317); /** PURE_IMPORTS_START _observable_empty,_observable_of,_observable_throwError PURE_IMPORTS_END */ @@ -29007,7 +35855,7 @@ var Notification = /*@__PURE__*/ (function () { /***/ }), -/* 231 */ +/* 311 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -29015,7 +35863,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "EMPTY", function() { return EMPTY; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "empty", function() { return empty; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "emptyScheduled", function() { return emptyScheduled; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); /** PURE_IMPORTS_START _Observable PURE_IMPORTS_END */ var EMPTY = /*@__PURE__*/ new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { return subscriber.complete(); }); @@ -29029,16 +35877,16 @@ function emptyScheduled(scheduler) { /***/ }), -/* 232 */ +/* 312 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "of", function() { return of; }); -/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(233); -/* harmony import */ var _fromArray__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(234); -/* harmony import */ var _empty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(231); -/* harmony import */ var _scalar__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(236); +/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(313); +/* harmony import */ var _fromArray__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(314); +/* harmony import */ var _empty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(311); +/* harmony import */ var _scalar__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(316); /** PURE_IMPORTS_START _util_isScheduler,_fromArray,_empty,_scalar PURE_IMPORTS_END */ @@ -29069,7 +35917,7 @@ function of() { /***/ }), -/* 233 */ +/* 313 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -29083,15 +35931,15 @@ function isScheduler(value) { /***/ }), -/* 234 */ +/* 314 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "fromArray", function() { return fromArray; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(204); -/* harmony import */ var _util_subscribeToArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(235); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(284); +/* harmony import */ var _util_subscribeToArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(315); /** PURE_IMPORTS_START _Observable,_Subscription,_util_subscribeToArray PURE_IMPORTS_END */ @@ -29122,7 +35970,7 @@ function fromArray(input, scheduler) { /***/ }), -/* 235 */ +/* 315 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -29143,13 +35991,13 @@ var subscribeToArray = function (array) { /***/ }), -/* 236 */ +/* 316 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "scalar", function() { return scalar; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); /** PURE_IMPORTS_START _Observable PURE_IMPORTS_END */ function scalar(value) { @@ -29165,13 +36013,13 @@ function scalar(value) { /***/ }), -/* 237 */ +/* 317 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "throwError", function() { return throwError; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); /** PURE_IMPORTS_START _Observable PURE_IMPORTS_END */ function throwError(error, scheduler) { @@ -29190,15 +36038,15 @@ function dispatch(_a) { /***/ }), -/* 238 */ +/* 318 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AsyncSubject", function() { return AsyncSubject; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(215); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(204); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(295); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(284); /** PURE_IMPORTS_START tslib,_Subject,_Subscription PURE_IMPORTS_END */ @@ -29249,14 +36097,14 @@ var AsyncSubject = /*@__PURE__*/ (function (_super) { /***/ }), -/* 239 */ +/* 319 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "asap", function() { return asap; }); -/* harmony import */ var _AsapAction__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(240); -/* harmony import */ var _AsapScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(242); +/* harmony import */ var _AsapAction__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(320); +/* harmony import */ var _AsapScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(322); /** PURE_IMPORTS_START _AsapAction,_AsapScheduler PURE_IMPORTS_END */ @@ -29265,15 +36113,15 @@ var asap = /*@__PURE__*/ new _AsapScheduler__WEBPACK_IMPORTED_MODULE_1__["AsapSc /***/ }), -/* 240 */ +/* 320 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AsapAction", function() { return AsapAction; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _util_Immediate__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(241); -/* harmony import */ var _AsyncAction__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(224); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _util_Immediate__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(321); +/* harmony import */ var _AsyncAction__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(304); /** PURE_IMPORTS_START tslib,_util_Immediate,_AsyncAction PURE_IMPORTS_END */ @@ -29316,7 +36164,7 @@ var AsapAction = /*@__PURE__*/ (function (_super) { /***/ }), -/* 241 */ +/* 321 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -29346,14 +36194,14 @@ var Immediate = { /***/ }), -/* 242 */ +/* 322 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AsapScheduler", function() { return AsapScheduler; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _AsyncScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(227); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _AsyncScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(307); /** PURE_IMPORTS_START tslib,_AsyncScheduler PURE_IMPORTS_END */ @@ -29390,14 +36238,14 @@ var AsapScheduler = /*@__PURE__*/ (function (_super) { /***/ }), -/* 243 */ +/* 323 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "async", function() { return async; }); -/* harmony import */ var _AsyncAction__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(224); -/* harmony import */ var _AsyncScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(227); +/* harmony import */ var _AsyncAction__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(304); +/* harmony import */ var _AsyncScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(307); /** PURE_IMPORTS_START _AsyncAction,_AsyncScheduler PURE_IMPORTS_END */ @@ -29406,14 +36254,14 @@ var async = /*@__PURE__*/ new _AsyncScheduler__WEBPACK_IMPORTED_MODULE_1__["Asyn /***/ }), -/* 244 */ +/* 324 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "animationFrame", function() { return animationFrame; }); -/* harmony import */ var _AnimationFrameAction__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(245); -/* harmony import */ var _AnimationFrameScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(246); +/* harmony import */ var _AnimationFrameAction__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(325); +/* harmony import */ var _AnimationFrameScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(326); /** PURE_IMPORTS_START _AnimationFrameAction,_AnimationFrameScheduler PURE_IMPORTS_END */ @@ -29422,14 +36270,14 @@ var animationFrame = /*@__PURE__*/ new _AnimationFrameScheduler__WEBPACK_IMPORTE /***/ }), -/* 245 */ +/* 325 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AnimationFrameAction", function() { return AnimationFrameAction; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _AsyncAction__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(224); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _AsyncAction__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(304); /** PURE_IMPORTS_START tslib,_AsyncAction PURE_IMPORTS_END */ @@ -29471,14 +36319,14 @@ var AnimationFrameAction = /*@__PURE__*/ (function (_super) { /***/ }), -/* 246 */ +/* 326 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AnimationFrameScheduler", function() { return AnimationFrameScheduler; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _AsyncScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(227); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _AsyncScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(307); /** PURE_IMPORTS_START tslib,_AsyncScheduler PURE_IMPORTS_END */ @@ -29515,16 +36363,16 @@ var AnimationFrameScheduler = /*@__PURE__*/ (function (_super) { /***/ }), -/* 247 */ +/* 327 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "VirtualTimeScheduler", function() { return VirtualTimeScheduler; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "VirtualAction", function() { return VirtualAction; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _AsyncAction__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(224); -/* harmony import */ var _AsyncScheduler__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(227); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _AsyncAction__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(304); +/* harmony import */ var _AsyncScheduler__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(307); /** PURE_IMPORTS_START tslib,_AsyncAction,_AsyncScheduler PURE_IMPORTS_END */ @@ -29636,7 +36484,7 @@ var VirtualAction = /*@__PURE__*/ (function (_super) { /***/ }), -/* 248 */ +/* 328 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -29650,13 +36498,13 @@ function identity(x) { /***/ }), -/* 249 */ +/* 329 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isObservable", function() { return isObservable; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); /** PURE_IMPORTS_START _Observable PURE_IMPORTS_END */ function isObservable(obj) { @@ -29666,13 +36514,13 @@ function isObservable(obj) { /***/ }), -/* 250 */ +/* 330 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ArgumentOutOfRangeError", function() { return ArgumentOutOfRangeError; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); /** PURE_IMPORTS_START tslib PURE_IMPORTS_END */ var ArgumentOutOfRangeError = /*@__PURE__*/ (function (_super) { @@ -29690,13 +36538,13 @@ var ArgumentOutOfRangeError = /*@__PURE__*/ (function (_super) { /***/ }), -/* 251 */ +/* 331 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "EmptyError", function() { return EmptyError; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); /** PURE_IMPORTS_START tslib PURE_IMPORTS_END */ var EmptyError = /*@__PURE__*/ (function (_super) { @@ -29714,13 +36562,13 @@ var EmptyError = /*@__PURE__*/ (function (_super) { /***/ }), -/* 252 */ +/* 332 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TimeoutError", function() { return TimeoutError; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); /** PURE_IMPORTS_START tslib PURE_IMPORTS_END */ var TimeoutError = /*@__PURE__*/ (function (_super) { @@ -29738,17 +36586,17 @@ var TimeoutError = /*@__PURE__*/ (function (_super) { /***/ }), -/* 253 */ +/* 333 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "bindCallback", function() { return bindCallback; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); -/* harmony import */ var _AsyncSubject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(238); -/* harmony import */ var _operators_map__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(254); -/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(205); -/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(233); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); +/* harmony import */ var _AsyncSubject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(318); +/* harmony import */ var _operators_map__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(334); +/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(285); +/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(313); /** PURE_IMPORTS_START _Observable,_AsyncSubject,_operators_map,_util_isArray,_util_isScheduler PURE_IMPORTS_END */ @@ -29851,15 +36699,15 @@ function dispatchError(state) { /***/ }), -/* 254 */ +/* 334 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "map", function() { return map; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MapOperator", function() { return MapOperator; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); /** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ @@ -29908,17 +36756,17 @@ var MapSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 255 */ +/* 335 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "bindNodeCallback", function() { return bindNodeCallback; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); -/* harmony import */ var _AsyncSubject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(238); -/* harmony import */ var _operators_map__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(254); -/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(233); -/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(205); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); +/* harmony import */ var _AsyncSubject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(318); +/* harmony import */ var _operators_map__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(334); +/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(313); +/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(285); /** PURE_IMPORTS_START _Observable,_AsyncSubject,_operators_map,_util_isScheduler,_util_isArray PURE_IMPORTS_END */ @@ -30029,7 +36877,7 @@ function dispatchError(arg) { /***/ }), -/* 256 */ +/* 336 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -30037,12 +36885,12 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "combineLatest", function() { return combineLatest; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CombineLatestOperator", function() { return CombineLatestOperator; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CombineLatestSubscriber", function() { return CombineLatestSubscriber; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(233); -/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(205); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(258); -/* harmony import */ var _fromArray__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(234); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(313); +/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(285); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(337); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(338); +/* harmony import */ var _fromArray__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(314); /** PURE_IMPORTS_START tslib,_util_isScheduler,_util_isArray,_OuterSubscriber,_util_subscribeToResult,_fromArray PURE_IMPORTS_END */ @@ -30147,14 +36995,14 @@ var CombineLatestSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 257 */ +/* 337 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "OuterSubscriber", function() { return OuterSubscriber; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); /** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ @@ -30179,14 +37027,14 @@ var OuterSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 258 */ +/* 338 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "subscribeToResult", function() { return subscribeToResult; }); -/* harmony import */ var _InnerSubscriber__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(259); -/* harmony import */ var _subscribeTo__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(260); +/* harmony import */ var _InnerSubscriber__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(339); +/* harmony import */ var _subscribeTo__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(340); /** PURE_IMPORTS_START _InnerSubscriber,_subscribeTo PURE_IMPORTS_END */ @@ -30198,14 +37046,14 @@ function subscribeToResult(outerSubscriber, result, outerValue, outerIndex) { /***/ }), -/* 259 */ +/* 339 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "InnerSubscriber", function() { return InnerSubscriber; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); /** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ @@ -30237,22 +37085,22 @@ var InnerSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 260 */ +/* 340 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "subscribeTo", function() { return subscribeTo; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); -/* harmony import */ var _subscribeToArray__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(235); -/* harmony import */ var _subscribeToPromise__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(261); -/* harmony import */ var _subscribeToIterable__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(262); -/* harmony import */ var _subscribeToObservable__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(264); -/* harmony import */ var _isArrayLike__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(265); -/* harmony import */ var _isPromise__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(266); -/* harmony import */ var _isObject__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(206); -/* harmony import */ var _symbol_iterator__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(263); -/* harmony import */ var _symbol_observable__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(211); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); +/* harmony import */ var _subscribeToArray__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(315); +/* harmony import */ var _subscribeToPromise__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(341); +/* harmony import */ var _subscribeToIterable__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(342); +/* harmony import */ var _subscribeToObservable__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(344); +/* harmony import */ var _isArrayLike__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(345); +/* harmony import */ var _isPromise__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(346); +/* harmony import */ var _isObject__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(286); +/* harmony import */ var _symbol_iterator__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(343); +/* harmony import */ var _symbol_observable__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(291); /** PURE_IMPORTS_START _Observable,_subscribeToArray,_subscribeToPromise,_subscribeToIterable,_subscribeToObservable,_isArrayLike,_isPromise,_isObject,_symbol_iterator,_symbol_observable PURE_IMPORTS_END */ @@ -30300,13 +37148,13 @@ var subscribeTo = function (result) { /***/ }), -/* 261 */ +/* 341 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "subscribeToPromise", function() { return subscribeToPromise; }); -/* harmony import */ var _hostReportError__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(203); +/* harmony import */ var _hostReportError__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(283); /** PURE_IMPORTS_START _hostReportError PURE_IMPORTS_END */ var subscribeToPromise = function (promise) { @@ -30325,13 +37173,13 @@ var subscribeToPromise = function (promise) { /***/ }), -/* 262 */ +/* 342 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "subscribeToIterable", function() { return subscribeToIterable; }); -/* harmony import */ var _symbol_iterator__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(263); +/* harmony import */ var _symbol_iterator__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(343); /** PURE_IMPORTS_START _symbol_iterator PURE_IMPORTS_END */ var subscribeToIterable = function (iterable) { @@ -30362,7 +37210,7 @@ var subscribeToIterable = function (iterable) { /***/ }), -/* 263 */ +/* 343 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -30383,13 +37231,13 @@ var $$iterator = iterator; /***/ }), -/* 264 */ +/* 344 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "subscribeToObservable", function() { return subscribeToObservable; }); -/* harmony import */ var _symbol_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(211); +/* harmony import */ var _symbol_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(291); /** PURE_IMPORTS_START _symbol_observable PURE_IMPORTS_END */ var subscribeToObservable = function (obj) { @@ -30407,7 +37255,7 @@ var subscribeToObservable = function (obj) { /***/ }), -/* 265 */ +/* 345 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -30419,7 +37267,7 @@ var isArrayLike = (function (x) { return x && typeof x.length === 'number' && ty /***/ }), -/* 266 */ +/* 346 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -30433,16 +37281,16 @@ function isPromise(value) { /***/ }), -/* 267 */ +/* 347 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "concat", function() { return concat; }); -/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(233); -/* harmony import */ var _of__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(232); -/* harmony import */ var _from__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(268); -/* harmony import */ var _operators_concatAll__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(274); +/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(313); +/* harmony import */ var _of__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(312); +/* harmony import */ var _from__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(348); +/* harmony import */ var _operators_concatAll__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(354); /** PURE_IMPORTS_START _util_isScheduler,_of,_from,_operators_concatAll PURE_IMPORTS_END */ @@ -30462,22 +37310,22 @@ function concat() { /***/ }), -/* 268 */ +/* 348 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "from", function() { return from; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); -/* harmony import */ var _util_isPromise__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(266); -/* harmony import */ var _util_isArrayLike__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(265); -/* harmony import */ var _util_isInteropObservable__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(269); -/* harmony import */ var _util_isIterable__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(270); -/* harmony import */ var _fromArray__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(234); -/* harmony import */ var _fromPromise__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(271); -/* harmony import */ var _fromIterable__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(272); -/* harmony import */ var _fromObservable__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(273); -/* harmony import */ var _util_subscribeTo__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(260); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); +/* harmony import */ var _util_isPromise__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(346); +/* harmony import */ var _util_isArrayLike__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(345); +/* harmony import */ var _util_isInteropObservable__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(349); +/* harmony import */ var _util_isIterable__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(350); +/* harmony import */ var _fromArray__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(314); +/* harmony import */ var _fromPromise__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(351); +/* harmony import */ var _fromIterable__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(352); +/* harmony import */ var _fromObservable__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(353); +/* harmony import */ var _util_subscribeTo__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(340); /** PURE_IMPORTS_START _Observable,_util_isPromise,_util_isArrayLike,_util_isInteropObservable,_util_isIterable,_fromArray,_fromPromise,_fromIterable,_fromObservable,_util_subscribeTo PURE_IMPORTS_END */ @@ -30516,13 +37364,13 @@ function from(input, scheduler) { /***/ }), -/* 269 */ +/* 349 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isInteropObservable", function() { return isInteropObservable; }); -/* harmony import */ var _symbol_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(211); +/* harmony import */ var _symbol_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(291); /** PURE_IMPORTS_START _symbol_observable PURE_IMPORTS_END */ function isInteropObservable(input) { @@ -30532,13 +37380,13 @@ function isInteropObservable(input) { /***/ }), -/* 270 */ +/* 350 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isIterable", function() { return isIterable; }); -/* harmony import */ var _symbol_iterator__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(263); +/* harmony import */ var _symbol_iterator__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(343); /** PURE_IMPORTS_START _symbol_iterator PURE_IMPORTS_END */ function isIterable(input) { @@ -30548,15 +37396,15 @@ function isIterable(input) { /***/ }), -/* 271 */ +/* 351 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "fromPromise", function() { return fromPromise; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(204); -/* harmony import */ var _util_subscribeToPromise__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(261); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(284); +/* harmony import */ var _util_subscribeToPromise__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(341); /** PURE_IMPORTS_START _Observable,_Subscription,_util_subscribeToPromise PURE_IMPORTS_END */ @@ -30586,16 +37434,16 @@ function fromPromise(input, scheduler) { /***/ }), -/* 272 */ +/* 352 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "fromIterable", function() { return fromIterable; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(204); -/* harmony import */ var _symbol_iterator__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(263); -/* harmony import */ var _util_subscribeToIterable__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(262); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(284); +/* harmony import */ var _symbol_iterator__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(343); +/* harmony import */ var _util_subscribeToIterable__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(342); /** PURE_IMPORTS_START _Observable,_Subscription,_symbol_iterator,_util_subscribeToIterable PURE_IMPORTS_END */ @@ -30651,16 +37499,16 @@ function fromIterable(input, scheduler) { /***/ }), -/* 273 */ +/* 353 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "fromObservable", function() { return fromObservable; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(204); -/* harmony import */ var _symbol_observable__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(211); -/* harmony import */ var _util_subscribeToObservable__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(264); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(284); +/* harmony import */ var _symbol_observable__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(291); +/* harmony import */ var _util_subscribeToObservable__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(344); /** PURE_IMPORTS_START _Observable,_Subscription,_symbol_observable,_util_subscribeToObservable PURE_IMPORTS_END */ @@ -30689,13 +37537,13 @@ function fromObservable(input, scheduler) { /***/ }), -/* 274 */ +/* 354 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "concatAll", function() { return concatAll; }); -/* harmony import */ var _mergeAll__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(275); +/* harmony import */ var _mergeAll__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(355); /** PURE_IMPORTS_START _mergeAll PURE_IMPORTS_END */ function concatAll() { @@ -30705,14 +37553,14 @@ function concatAll() { /***/ }), -/* 275 */ +/* 355 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "mergeAll", function() { return mergeAll; }); -/* harmony import */ var _mergeMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); -/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(248); +/* harmony import */ var _mergeMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(356); +/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(328); /** PURE_IMPORTS_START _mergeMap,_util_identity PURE_IMPORTS_END */ @@ -30726,7 +37574,7 @@ function mergeAll(concurrent) { /***/ }), -/* 276 */ +/* 356 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -30734,11 +37582,11 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "mergeMap", function() { return mergeMap; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MergeMapOperator", function() { return MergeMapOperator; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MergeMapSubscriber", function() { return MergeMapSubscriber; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(258); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(257); -/* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(254); -/* harmony import */ var _observable_from__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(268); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(338); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(337); +/* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(334); +/* harmony import */ var _observable_from__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(348); /** PURE_IMPORTS_START tslib,_util_subscribeToResult,_OuterSubscriber,_map,_observable_from PURE_IMPORTS_END */ @@ -30837,15 +37685,15 @@ var MergeMapSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 277 */ +/* 357 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "defer", function() { return defer; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); -/* harmony import */ var _from__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(268); -/* harmony import */ var _empty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(231); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); +/* harmony import */ var _from__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(348); +/* harmony import */ var _empty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(311); /** PURE_IMPORTS_START _Observable,_from,_empty PURE_IMPORTS_END */ @@ -30868,19 +37716,19 @@ function defer(observableFactory) { /***/ }), -/* 278 */ +/* 358 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "forkJoin", function() { return forkJoin; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(196); -/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(205); -/* harmony import */ var _empty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(231); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(258); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(257); -/* harmony import */ var _operators_map__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(254); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(276); +/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(285); +/* harmony import */ var _empty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(311); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(338); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(337); +/* harmony import */ var _operators_map__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(334); /** PURE_IMPORTS_START tslib,_Observable,_util_isArray,_empty,_util_subscribeToResult,_OuterSubscriber,_operators_map PURE_IMPORTS_END */ @@ -30958,16 +37806,16 @@ var ForkJoinSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 279 */ +/* 359 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "fromEvent", function() { return fromEvent; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); -/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(205); -/* harmony import */ var _util_isFunction__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(200); -/* harmony import */ var _operators_map__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(254); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); +/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(285); +/* harmony import */ var _util_isFunction__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(280); +/* harmony import */ var _operators_map__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(334); /** PURE_IMPORTS_START _Observable,_util_isArray,_util_isFunction,_operators_map PURE_IMPORTS_END */ @@ -31034,16 +37882,16 @@ function isEventTarget(sourceObj) { /***/ }), -/* 280 */ +/* 360 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "fromEventPattern", function() { return fromEventPattern; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); -/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(205); -/* harmony import */ var _util_isFunction__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(200); -/* harmony import */ var _operators_map__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(254); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); +/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(285); +/* harmony import */ var _util_isFunction__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(280); +/* harmony import */ var _operators_map__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(334); /** PURE_IMPORTS_START _Observable,_util_isArray,_util_isFunction,_operators_map PURE_IMPORTS_END */ @@ -31079,15 +37927,15 @@ function fromEventPattern(addHandler, removeHandler, resultSelector) { /***/ }), -/* 281 */ +/* 361 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "generate", function() { return generate; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); -/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(248); -/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(233); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); +/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(328); +/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(313); /** PURE_IMPORTS_START _Observable,_util_identity,_util_isScheduler PURE_IMPORTS_END */ @@ -31216,14 +38064,14 @@ function dispatch(state) { /***/ }), -/* 282 */ +/* 362 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "iif", function() { return iif; }); -/* harmony import */ var _defer__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(277); -/* harmony import */ var _empty__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(231); +/* harmony import */ var _defer__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(357); +/* harmony import */ var _empty__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(311); /** PURE_IMPORTS_START _defer,_empty PURE_IMPORTS_END */ @@ -31240,15 +38088,15 @@ function iif(condition, trueResult, falseResult) { /***/ }), -/* 283 */ +/* 363 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "interval", function() { return interval; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(243); -/* harmony import */ var _util_isNumeric__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(284); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); +/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(323); +/* harmony import */ var _util_isNumeric__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(364); /** PURE_IMPORTS_START _Observable,_scheduler_async,_util_isNumeric PURE_IMPORTS_END */ @@ -31280,13 +38128,13 @@ function dispatch(state) { /***/ }), -/* 284 */ +/* 364 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isNumeric", function() { return isNumeric; }); -/* harmony import */ var _isArray__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(205); +/* harmony import */ var _isArray__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(285); /** PURE_IMPORTS_START _isArray PURE_IMPORTS_END */ function isNumeric(val) { @@ -31296,16 +38144,16 @@ function isNumeric(val) { /***/ }), -/* 285 */ +/* 365 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "merge", function() { return merge; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); -/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(233); -/* harmony import */ var _operators_mergeAll__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(275); -/* harmony import */ var _fromArray__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(234); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); +/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(313); +/* harmony import */ var _operators_mergeAll__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(355); +/* harmony import */ var _fromArray__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(314); /** PURE_IMPORTS_START _Observable,_util_isScheduler,_operators_mergeAll,_fromArray PURE_IMPORTS_END */ @@ -31337,15 +38185,15 @@ function merge() { /***/ }), -/* 286 */ +/* 366 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "NEVER", function() { return NEVER; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "never", function() { return never; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); -/* harmony import */ var _util_noop__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(213); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); +/* harmony import */ var _util_noop__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(293); /** PURE_IMPORTS_START _Observable,_util_noop PURE_IMPORTS_END */ @@ -31357,16 +38205,16 @@ function never() { /***/ }), -/* 287 */ +/* 367 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "onErrorResumeNext", function() { return onErrorResumeNext; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); -/* harmony import */ var _from__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(268); -/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(205); -/* harmony import */ var _empty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(231); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); +/* harmony import */ var _from__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(348); +/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(285); +/* harmony import */ var _empty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(311); /** PURE_IMPORTS_START _Observable,_from,_util_isArray,_empty PURE_IMPORTS_END */ @@ -31397,15 +38245,15 @@ function onErrorResumeNext() { /***/ }), -/* 288 */ +/* 368 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "pairs", function() { return pairs; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "dispatch", function() { return dispatch; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(204); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(284); /** PURE_IMPORTS_START _Observable,_Subscription PURE_IMPORTS_END */ @@ -31448,7 +38296,7 @@ function dispatch(state) { /***/ }), -/* 289 */ +/* 369 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -31456,11 +38304,11 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "race", function() { return race; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "RaceOperator", function() { return RaceOperator; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "RaceSubscriber", function() { return RaceSubscriber; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(205); -/* harmony import */ var _fromArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(234); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(258); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(285); +/* harmony import */ var _fromArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(314); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(337); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(338); /** PURE_IMPORTS_START tslib,_util_isArray,_fromArray,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ @@ -31542,14 +38390,14 @@ var RaceSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 290 */ +/* 370 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "range", function() { return range; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "dispatch", function() { return dispatch; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); /** PURE_IMPORTS_START _Observable PURE_IMPORTS_END */ function range(start, count, scheduler) { @@ -31600,16 +38448,16 @@ function dispatch(state) { /***/ }), -/* 291 */ +/* 371 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "timer", function() { return timer; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(243); -/* harmony import */ var _util_isNumeric__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(284); -/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(233); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); +/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(323); +/* harmony import */ var _util_isNumeric__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(364); +/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(313); /** PURE_IMPORTS_START _Observable,_scheduler_async,_util_isNumeric,_util_isScheduler PURE_IMPORTS_END */ @@ -31654,15 +38502,15 @@ function dispatch(state) { /***/ }), -/* 292 */ +/* 372 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "using", function() { return using; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(196); -/* harmony import */ var _from__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(268); -/* harmony import */ var _empty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(231); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); +/* harmony import */ var _from__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(348); +/* harmony import */ var _empty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(311); /** PURE_IMPORTS_START _Observable,_from,_empty PURE_IMPORTS_END */ @@ -31699,7 +38547,7 @@ function using(resourceFactory, observableFactory) { /***/ }), -/* 293 */ +/* 373 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -31707,13 +38555,13 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "zip", function() { return zip; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ZipOperator", function() { return ZipOperator; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ZipSubscriber", function() { return ZipSubscriber; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _fromArray__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(234); -/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(205); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(198); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(258); -/* harmony import */ var _internal_symbol_iterator__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(263); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _fromArray__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(314); +/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(285); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(278); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(337); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(338); +/* harmony import */ var _internal_symbol_iterator__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(343); /** PURE_IMPORTS_START tslib,_fromArray,_util_isArray,_Subscriber,_OuterSubscriber,_util_subscribeToResult,_.._internal_symbol_iterator PURE_IMPORTS_END */ @@ -31933,320 +38781,320 @@ var ZipBufferIterator = /*@__PURE__*/ (function (_super) { /***/ }), -/* 294 */ +/* 374 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); -/* harmony import */ var _internal_operators_audit__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(295); +/* harmony import */ var _internal_operators_audit__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(375); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "audit", function() { return _internal_operators_audit__WEBPACK_IMPORTED_MODULE_0__["audit"]; }); -/* harmony import */ var _internal_operators_auditTime__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(296); +/* harmony import */ var _internal_operators_auditTime__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(376); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "auditTime", function() { return _internal_operators_auditTime__WEBPACK_IMPORTED_MODULE_1__["auditTime"]; }); -/* harmony import */ var _internal_operators_buffer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(297); +/* harmony import */ var _internal_operators_buffer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(377); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buffer", function() { return _internal_operators_buffer__WEBPACK_IMPORTED_MODULE_2__["buffer"]; }); -/* harmony import */ var _internal_operators_bufferCount__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(298); +/* harmony import */ var _internal_operators_bufferCount__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(378); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bufferCount", function() { return _internal_operators_bufferCount__WEBPACK_IMPORTED_MODULE_3__["bufferCount"]; }); -/* harmony import */ var _internal_operators_bufferTime__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(299); +/* harmony import */ var _internal_operators_bufferTime__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(379); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bufferTime", function() { return _internal_operators_bufferTime__WEBPACK_IMPORTED_MODULE_4__["bufferTime"]; }); -/* harmony import */ var _internal_operators_bufferToggle__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(300); +/* harmony import */ var _internal_operators_bufferToggle__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(380); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bufferToggle", function() { return _internal_operators_bufferToggle__WEBPACK_IMPORTED_MODULE_5__["bufferToggle"]; }); -/* harmony import */ var _internal_operators_bufferWhen__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(301); +/* harmony import */ var _internal_operators_bufferWhen__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(381); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bufferWhen", function() { return _internal_operators_bufferWhen__WEBPACK_IMPORTED_MODULE_6__["bufferWhen"]; }); -/* harmony import */ var _internal_operators_catchError__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(302); +/* harmony import */ var _internal_operators_catchError__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(382); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "catchError", function() { return _internal_operators_catchError__WEBPACK_IMPORTED_MODULE_7__["catchError"]; }); -/* harmony import */ var _internal_operators_combineAll__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(303); +/* harmony import */ var _internal_operators_combineAll__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(383); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "combineAll", function() { return _internal_operators_combineAll__WEBPACK_IMPORTED_MODULE_8__["combineAll"]; }); -/* harmony import */ var _internal_operators_combineLatest__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(304); +/* harmony import */ var _internal_operators_combineLatest__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(384); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "combineLatest", function() { return _internal_operators_combineLatest__WEBPACK_IMPORTED_MODULE_9__["combineLatest"]; }); -/* harmony import */ var _internal_operators_concat__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(305); +/* harmony import */ var _internal_operators_concat__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(385); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concat", function() { return _internal_operators_concat__WEBPACK_IMPORTED_MODULE_10__["concat"]; }); -/* harmony import */ var _internal_operators_concatAll__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(274); +/* harmony import */ var _internal_operators_concatAll__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(354); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concatAll", function() { return _internal_operators_concatAll__WEBPACK_IMPORTED_MODULE_11__["concatAll"]; }); -/* harmony import */ var _internal_operators_concatMap__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(306); +/* harmony import */ var _internal_operators_concatMap__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(386); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concatMap", function() { return _internal_operators_concatMap__WEBPACK_IMPORTED_MODULE_12__["concatMap"]; }); -/* harmony import */ var _internal_operators_concatMapTo__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(307); +/* harmony import */ var _internal_operators_concatMapTo__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(387); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concatMapTo", function() { return _internal_operators_concatMapTo__WEBPACK_IMPORTED_MODULE_13__["concatMapTo"]; }); -/* harmony import */ var _internal_operators_count__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(308); +/* harmony import */ var _internal_operators_count__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(388); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "count", function() { return _internal_operators_count__WEBPACK_IMPORTED_MODULE_14__["count"]; }); -/* harmony import */ var _internal_operators_debounce__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(309); +/* harmony import */ var _internal_operators_debounce__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(389); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "debounce", function() { return _internal_operators_debounce__WEBPACK_IMPORTED_MODULE_15__["debounce"]; }); -/* harmony import */ var _internal_operators_debounceTime__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(310); +/* harmony import */ var _internal_operators_debounceTime__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(390); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "debounceTime", function() { return _internal_operators_debounceTime__WEBPACK_IMPORTED_MODULE_16__["debounceTime"]; }); -/* harmony import */ var _internal_operators_defaultIfEmpty__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(311); +/* harmony import */ var _internal_operators_defaultIfEmpty__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(391); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "defaultIfEmpty", function() { return _internal_operators_defaultIfEmpty__WEBPACK_IMPORTED_MODULE_17__["defaultIfEmpty"]; }); -/* harmony import */ var _internal_operators_delay__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(312); +/* harmony import */ var _internal_operators_delay__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(392); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "delay", function() { return _internal_operators_delay__WEBPACK_IMPORTED_MODULE_18__["delay"]; }); -/* harmony import */ var _internal_operators_delayWhen__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(314); +/* harmony import */ var _internal_operators_delayWhen__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(394); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "delayWhen", function() { return _internal_operators_delayWhen__WEBPACK_IMPORTED_MODULE_19__["delayWhen"]; }); -/* harmony import */ var _internal_operators_dematerialize__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(315); +/* harmony import */ var _internal_operators_dematerialize__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(395); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "dematerialize", function() { return _internal_operators_dematerialize__WEBPACK_IMPORTED_MODULE_20__["dematerialize"]; }); -/* harmony import */ var _internal_operators_distinct__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__(316); +/* harmony import */ var _internal_operators_distinct__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__(396); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "distinct", function() { return _internal_operators_distinct__WEBPACK_IMPORTED_MODULE_21__["distinct"]; }); -/* harmony import */ var _internal_operators_distinctUntilChanged__WEBPACK_IMPORTED_MODULE_22__ = __webpack_require__(317); +/* harmony import */ var _internal_operators_distinctUntilChanged__WEBPACK_IMPORTED_MODULE_22__ = __webpack_require__(397); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "distinctUntilChanged", function() { return _internal_operators_distinctUntilChanged__WEBPACK_IMPORTED_MODULE_22__["distinctUntilChanged"]; }); -/* harmony import */ var _internal_operators_distinctUntilKeyChanged__WEBPACK_IMPORTED_MODULE_23__ = __webpack_require__(318); +/* harmony import */ var _internal_operators_distinctUntilKeyChanged__WEBPACK_IMPORTED_MODULE_23__ = __webpack_require__(398); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "distinctUntilKeyChanged", function() { return _internal_operators_distinctUntilKeyChanged__WEBPACK_IMPORTED_MODULE_23__["distinctUntilKeyChanged"]; }); -/* harmony import */ var _internal_operators_elementAt__WEBPACK_IMPORTED_MODULE_24__ = __webpack_require__(319); +/* harmony import */ var _internal_operators_elementAt__WEBPACK_IMPORTED_MODULE_24__ = __webpack_require__(399); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "elementAt", function() { return _internal_operators_elementAt__WEBPACK_IMPORTED_MODULE_24__["elementAt"]; }); -/* harmony import */ var _internal_operators_endWith__WEBPACK_IMPORTED_MODULE_25__ = __webpack_require__(324); +/* harmony import */ var _internal_operators_endWith__WEBPACK_IMPORTED_MODULE_25__ = __webpack_require__(404); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "endWith", function() { return _internal_operators_endWith__WEBPACK_IMPORTED_MODULE_25__["endWith"]; }); -/* harmony import */ var _internal_operators_every__WEBPACK_IMPORTED_MODULE_26__ = __webpack_require__(325); +/* harmony import */ var _internal_operators_every__WEBPACK_IMPORTED_MODULE_26__ = __webpack_require__(405); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "every", function() { return _internal_operators_every__WEBPACK_IMPORTED_MODULE_26__["every"]; }); -/* harmony import */ var _internal_operators_exhaust__WEBPACK_IMPORTED_MODULE_27__ = __webpack_require__(326); +/* harmony import */ var _internal_operators_exhaust__WEBPACK_IMPORTED_MODULE_27__ = __webpack_require__(406); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "exhaust", function() { return _internal_operators_exhaust__WEBPACK_IMPORTED_MODULE_27__["exhaust"]; }); -/* harmony import */ var _internal_operators_exhaustMap__WEBPACK_IMPORTED_MODULE_28__ = __webpack_require__(327); +/* harmony import */ var _internal_operators_exhaustMap__WEBPACK_IMPORTED_MODULE_28__ = __webpack_require__(407); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "exhaustMap", function() { return _internal_operators_exhaustMap__WEBPACK_IMPORTED_MODULE_28__["exhaustMap"]; }); -/* harmony import */ var _internal_operators_expand__WEBPACK_IMPORTED_MODULE_29__ = __webpack_require__(328); +/* harmony import */ var _internal_operators_expand__WEBPACK_IMPORTED_MODULE_29__ = __webpack_require__(408); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "expand", function() { return _internal_operators_expand__WEBPACK_IMPORTED_MODULE_29__["expand"]; }); -/* harmony import */ var _internal_operators_filter__WEBPACK_IMPORTED_MODULE_30__ = __webpack_require__(320); +/* harmony import */ var _internal_operators_filter__WEBPACK_IMPORTED_MODULE_30__ = __webpack_require__(400); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "filter", function() { return _internal_operators_filter__WEBPACK_IMPORTED_MODULE_30__["filter"]; }); -/* harmony import */ var _internal_operators_finalize__WEBPACK_IMPORTED_MODULE_31__ = __webpack_require__(329); +/* harmony import */ var _internal_operators_finalize__WEBPACK_IMPORTED_MODULE_31__ = __webpack_require__(409); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "finalize", function() { return _internal_operators_finalize__WEBPACK_IMPORTED_MODULE_31__["finalize"]; }); -/* harmony import */ var _internal_operators_find__WEBPACK_IMPORTED_MODULE_32__ = __webpack_require__(330); +/* harmony import */ var _internal_operators_find__WEBPACK_IMPORTED_MODULE_32__ = __webpack_require__(410); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "find", function() { return _internal_operators_find__WEBPACK_IMPORTED_MODULE_32__["find"]; }); -/* harmony import */ var _internal_operators_findIndex__WEBPACK_IMPORTED_MODULE_33__ = __webpack_require__(331); +/* harmony import */ var _internal_operators_findIndex__WEBPACK_IMPORTED_MODULE_33__ = __webpack_require__(411); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "findIndex", function() { return _internal_operators_findIndex__WEBPACK_IMPORTED_MODULE_33__["findIndex"]; }); -/* harmony import */ var _internal_operators_first__WEBPACK_IMPORTED_MODULE_34__ = __webpack_require__(332); +/* harmony import */ var _internal_operators_first__WEBPACK_IMPORTED_MODULE_34__ = __webpack_require__(412); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "first", function() { return _internal_operators_first__WEBPACK_IMPORTED_MODULE_34__["first"]; }); -/* harmony import */ var _internal_operators_groupBy__WEBPACK_IMPORTED_MODULE_35__ = __webpack_require__(219); +/* harmony import */ var _internal_operators_groupBy__WEBPACK_IMPORTED_MODULE_35__ = __webpack_require__(299); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "groupBy", function() { return _internal_operators_groupBy__WEBPACK_IMPORTED_MODULE_35__["groupBy"]; }); -/* harmony import */ var _internal_operators_ignoreElements__WEBPACK_IMPORTED_MODULE_36__ = __webpack_require__(333); +/* harmony import */ var _internal_operators_ignoreElements__WEBPACK_IMPORTED_MODULE_36__ = __webpack_require__(413); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "ignoreElements", function() { return _internal_operators_ignoreElements__WEBPACK_IMPORTED_MODULE_36__["ignoreElements"]; }); -/* harmony import */ var _internal_operators_isEmpty__WEBPACK_IMPORTED_MODULE_37__ = __webpack_require__(334); +/* harmony import */ var _internal_operators_isEmpty__WEBPACK_IMPORTED_MODULE_37__ = __webpack_require__(414); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "isEmpty", function() { return _internal_operators_isEmpty__WEBPACK_IMPORTED_MODULE_37__["isEmpty"]; }); -/* harmony import */ var _internal_operators_last__WEBPACK_IMPORTED_MODULE_38__ = __webpack_require__(335); +/* harmony import */ var _internal_operators_last__WEBPACK_IMPORTED_MODULE_38__ = __webpack_require__(415); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "last", function() { return _internal_operators_last__WEBPACK_IMPORTED_MODULE_38__["last"]; }); -/* harmony import */ var _internal_operators_map__WEBPACK_IMPORTED_MODULE_39__ = __webpack_require__(254); +/* harmony import */ var _internal_operators_map__WEBPACK_IMPORTED_MODULE_39__ = __webpack_require__(334); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "map", function() { return _internal_operators_map__WEBPACK_IMPORTED_MODULE_39__["map"]; }); -/* harmony import */ var _internal_operators_mapTo__WEBPACK_IMPORTED_MODULE_40__ = __webpack_require__(337); +/* harmony import */ var _internal_operators_mapTo__WEBPACK_IMPORTED_MODULE_40__ = __webpack_require__(417); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "mapTo", function() { return _internal_operators_mapTo__WEBPACK_IMPORTED_MODULE_40__["mapTo"]; }); -/* harmony import */ var _internal_operators_materialize__WEBPACK_IMPORTED_MODULE_41__ = __webpack_require__(338); +/* harmony import */ var _internal_operators_materialize__WEBPACK_IMPORTED_MODULE_41__ = __webpack_require__(418); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "materialize", function() { return _internal_operators_materialize__WEBPACK_IMPORTED_MODULE_41__["materialize"]; }); -/* harmony import */ var _internal_operators_max__WEBPACK_IMPORTED_MODULE_42__ = __webpack_require__(339); +/* harmony import */ var _internal_operators_max__WEBPACK_IMPORTED_MODULE_42__ = __webpack_require__(419); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "max", function() { return _internal_operators_max__WEBPACK_IMPORTED_MODULE_42__["max"]; }); -/* harmony import */ var _internal_operators_merge__WEBPACK_IMPORTED_MODULE_43__ = __webpack_require__(342); +/* harmony import */ var _internal_operators_merge__WEBPACK_IMPORTED_MODULE_43__ = __webpack_require__(422); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "merge", function() { return _internal_operators_merge__WEBPACK_IMPORTED_MODULE_43__["merge"]; }); -/* harmony import */ var _internal_operators_mergeAll__WEBPACK_IMPORTED_MODULE_44__ = __webpack_require__(275); +/* harmony import */ var _internal_operators_mergeAll__WEBPACK_IMPORTED_MODULE_44__ = __webpack_require__(355); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "mergeAll", function() { return _internal_operators_mergeAll__WEBPACK_IMPORTED_MODULE_44__["mergeAll"]; }); -/* harmony import */ var _internal_operators_mergeMap__WEBPACK_IMPORTED_MODULE_45__ = __webpack_require__(276); +/* harmony import */ var _internal_operators_mergeMap__WEBPACK_IMPORTED_MODULE_45__ = __webpack_require__(356); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "mergeMap", function() { return _internal_operators_mergeMap__WEBPACK_IMPORTED_MODULE_45__["mergeMap"]; }); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "flatMap", function() { return _internal_operators_mergeMap__WEBPACK_IMPORTED_MODULE_45__["mergeMap"]; }); -/* harmony import */ var _internal_operators_mergeMapTo__WEBPACK_IMPORTED_MODULE_46__ = __webpack_require__(343); +/* harmony import */ var _internal_operators_mergeMapTo__WEBPACK_IMPORTED_MODULE_46__ = __webpack_require__(423); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "mergeMapTo", function() { return _internal_operators_mergeMapTo__WEBPACK_IMPORTED_MODULE_46__["mergeMapTo"]; }); -/* harmony import */ var _internal_operators_mergeScan__WEBPACK_IMPORTED_MODULE_47__ = __webpack_require__(344); +/* harmony import */ var _internal_operators_mergeScan__WEBPACK_IMPORTED_MODULE_47__ = __webpack_require__(424); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "mergeScan", function() { return _internal_operators_mergeScan__WEBPACK_IMPORTED_MODULE_47__["mergeScan"]; }); -/* harmony import */ var _internal_operators_min__WEBPACK_IMPORTED_MODULE_48__ = __webpack_require__(345); +/* harmony import */ var _internal_operators_min__WEBPACK_IMPORTED_MODULE_48__ = __webpack_require__(425); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "min", function() { return _internal_operators_min__WEBPACK_IMPORTED_MODULE_48__["min"]; }); -/* harmony import */ var _internal_operators_multicast__WEBPACK_IMPORTED_MODULE_49__ = __webpack_require__(346); +/* harmony import */ var _internal_operators_multicast__WEBPACK_IMPORTED_MODULE_49__ = __webpack_require__(426); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "multicast", function() { return _internal_operators_multicast__WEBPACK_IMPORTED_MODULE_49__["multicast"]; }); -/* harmony import */ var _internal_operators_observeOn__WEBPACK_IMPORTED_MODULE_50__ = __webpack_require__(229); +/* harmony import */ var _internal_operators_observeOn__WEBPACK_IMPORTED_MODULE_50__ = __webpack_require__(309); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "observeOn", function() { return _internal_operators_observeOn__WEBPACK_IMPORTED_MODULE_50__["observeOn"]; }); -/* harmony import */ var _internal_operators_onErrorResumeNext__WEBPACK_IMPORTED_MODULE_51__ = __webpack_require__(347); +/* harmony import */ var _internal_operators_onErrorResumeNext__WEBPACK_IMPORTED_MODULE_51__ = __webpack_require__(427); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "onErrorResumeNext", function() { return _internal_operators_onErrorResumeNext__WEBPACK_IMPORTED_MODULE_51__["onErrorResumeNext"]; }); -/* harmony import */ var _internal_operators_pairwise__WEBPACK_IMPORTED_MODULE_52__ = __webpack_require__(348); +/* harmony import */ var _internal_operators_pairwise__WEBPACK_IMPORTED_MODULE_52__ = __webpack_require__(428); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "pairwise", function() { return _internal_operators_pairwise__WEBPACK_IMPORTED_MODULE_52__["pairwise"]; }); -/* harmony import */ var _internal_operators_partition__WEBPACK_IMPORTED_MODULE_53__ = __webpack_require__(349); +/* harmony import */ var _internal_operators_partition__WEBPACK_IMPORTED_MODULE_53__ = __webpack_require__(429); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "partition", function() { return _internal_operators_partition__WEBPACK_IMPORTED_MODULE_53__["partition"]; }); -/* harmony import */ var _internal_operators_pluck__WEBPACK_IMPORTED_MODULE_54__ = __webpack_require__(351); +/* harmony import */ var _internal_operators_pluck__WEBPACK_IMPORTED_MODULE_54__ = __webpack_require__(431); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "pluck", function() { return _internal_operators_pluck__WEBPACK_IMPORTED_MODULE_54__["pluck"]; }); -/* harmony import */ var _internal_operators_publish__WEBPACK_IMPORTED_MODULE_55__ = __webpack_require__(352); +/* harmony import */ var _internal_operators_publish__WEBPACK_IMPORTED_MODULE_55__ = __webpack_require__(432); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "publish", function() { return _internal_operators_publish__WEBPACK_IMPORTED_MODULE_55__["publish"]; }); -/* harmony import */ var _internal_operators_publishBehavior__WEBPACK_IMPORTED_MODULE_56__ = __webpack_require__(353); +/* harmony import */ var _internal_operators_publishBehavior__WEBPACK_IMPORTED_MODULE_56__ = __webpack_require__(433); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "publishBehavior", function() { return _internal_operators_publishBehavior__WEBPACK_IMPORTED_MODULE_56__["publishBehavior"]; }); -/* harmony import */ var _internal_operators_publishLast__WEBPACK_IMPORTED_MODULE_57__ = __webpack_require__(354); +/* harmony import */ var _internal_operators_publishLast__WEBPACK_IMPORTED_MODULE_57__ = __webpack_require__(434); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "publishLast", function() { return _internal_operators_publishLast__WEBPACK_IMPORTED_MODULE_57__["publishLast"]; }); -/* harmony import */ var _internal_operators_publishReplay__WEBPACK_IMPORTED_MODULE_58__ = __webpack_require__(355); +/* harmony import */ var _internal_operators_publishReplay__WEBPACK_IMPORTED_MODULE_58__ = __webpack_require__(435); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "publishReplay", function() { return _internal_operators_publishReplay__WEBPACK_IMPORTED_MODULE_58__["publishReplay"]; }); -/* harmony import */ var _internal_operators_race__WEBPACK_IMPORTED_MODULE_59__ = __webpack_require__(356); +/* harmony import */ var _internal_operators_race__WEBPACK_IMPORTED_MODULE_59__ = __webpack_require__(436); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "race", function() { return _internal_operators_race__WEBPACK_IMPORTED_MODULE_59__["race"]; }); -/* harmony import */ var _internal_operators_reduce__WEBPACK_IMPORTED_MODULE_60__ = __webpack_require__(340); +/* harmony import */ var _internal_operators_reduce__WEBPACK_IMPORTED_MODULE_60__ = __webpack_require__(420); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "reduce", function() { return _internal_operators_reduce__WEBPACK_IMPORTED_MODULE_60__["reduce"]; }); -/* harmony import */ var _internal_operators_repeat__WEBPACK_IMPORTED_MODULE_61__ = __webpack_require__(357); +/* harmony import */ var _internal_operators_repeat__WEBPACK_IMPORTED_MODULE_61__ = __webpack_require__(437); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "repeat", function() { return _internal_operators_repeat__WEBPACK_IMPORTED_MODULE_61__["repeat"]; }); -/* harmony import */ var _internal_operators_repeatWhen__WEBPACK_IMPORTED_MODULE_62__ = __webpack_require__(358); +/* harmony import */ var _internal_operators_repeatWhen__WEBPACK_IMPORTED_MODULE_62__ = __webpack_require__(438); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "repeatWhen", function() { return _internal_operators_repeatWhen__WEBPACK_IMPORTED_MODULE_62__["repeatWhen"]; }); -/* harmony import */ var _internal_operators_retry__WEBPACK_IMPORTED_MODULE_63__ = __webpack_require__(359); +/* harmony import */ var _internal_operators_retry__WEBPACK_IMPORTED_MODULE_63__ = __webpack_require__(439); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "retry", function() { return _internal_operators_retry__WEBPACK_IMPORTED_MODULE_63__["retry"]; }); -/* harmony import */ var _internal_operators_retryWhen__WEBPACK_IMPORTED_MODULE_64__ = __webpack_require__(360); +/* harmony import */ var _internal_operators_retryWhen__WEBPACK_IMPORTED_MODULE_64__ = __webpack_require__(440); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "retryWhen", function() { return _internal_operators_retryWhen__WEBPACK_IMPORTED_MODULE_64__["retryWhen"]; }); -/* harmony import */ var _internal_operators_refCount__WEBPACK_IMPORTED_MODULE_65__ = __webpack_require__(218); +/* harmony import */ var _internal_operators_refCount__WEBPACK_IMPORTED_MODULE_65__ = __webpack_require__(298); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "refCount", function() { return _internal_operators_refCount__WEBPACK_IMPORTED_MODULE_65__["refCount"]; }); -/* harmony import */ var _internal_operators_sample__WEBPACK_IMPORTED_MODULE_66__ = __webpack_require__(361); +/* harmony import */ var _internal_operators_sample__WEBPACK_IMPORTED_MODULE_66__ = __webpack_require__(441); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "sample", function() { return _internal_operators_sample__WEBPACK_IMPORTED_MODULE_66__["sample"]; }); -/* harmony import */ var _internal_operators_sampleTime__WEBPACK_IMPORTED_MODULE_67__ = __webpack_require__(362); +/* harmony import */ var _internal_operators_sampleTime__WEBPACK_IMPORTED_MODULE_67__ = __webpack_require__(442); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "sampleTime", function() { return _internal_operators_sampleTime__WEBPACK_IMPORTED_MODULE_67__["sampleTime"]; }); -/* harmony import */ var _internal_operators_scan__WEBPACK_IMPORTED_MODULE_68__ = __webpack_require__(341); +/* harmony import */ var _internal_operators_scan__WEBPACK_IMPORTED_MODULE_68__ = __webpack_require__(421); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "scan", function() { return _internal_operators_scan__WEBPACK_IMPORTED_MODULE_68__["scan"]; }); -/* harmony import */ var _internal_operators_sequenceEqual__WEBPACK_IMPORTED_MODULE_69__ = __webpack_require__(363); +/* harmony import */ var _internal_operators_sequenceEqual__WEBPACK_IMPORTED_MODULE_69__ = __webpack_require__(443); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "sequenceEqual", function() { return _internal_operators_sequenceEqual__WEBPACK_IMPORTED_MODULE_69__["sequenceEqual"]; }); -/* harmony import */ var _internal_operators_share__WEBPACK_IMPORTED_MODULE_70__ = __webpack_require__(364); +/* harmony import */ var _internal_operators_share__WEBPACK_IMPORTED_MODULE_70__ = __webpack_require__(444); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "share", function() { return _internal_operators_share__WEBPACK_IMPORTED_MODULE_70__["share"]; }); -/* harmony import */ var _internal_operators_shareReplay__WEBPACK_IMPORTED_MODULE_71__ = __webpack_require__(365); +/* harmony import */ var _internal_operators_shareReplay__WEBPACK_IMPORTED_MODULE_71__ = __webpack_require__(445); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "shareReplay", function() { return _internal_operators_shareReplay__WEBPACK_IMPORTED_MODULE_71__["shareReplay"]; }); -/* harmony import */ var _internal_operators_single__WEBPACK_IMPORTED_MODULE_72__ = __webpack_require__(366); +/* harmony import */ var _internal_operators_single__WEBPACK_IMPORTED_MODULE_72__ = __webpack_require__(446); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "single", function() { return _internal_operators_single__WEBPACK_IMPORTED_MODULE_72__["single"]; }); -/* harmony import */ var _internal_operators_skip__WEBPACK_IMPORTED_MODULE_73__ = __webpack_require__(367); +/* harmony import */ var _internal_operators_skip__WEBPACK_IMPORTED_MODULE_73__ = __webpack_require__(447); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "skip", function() { return _internal_operators_skip__WEBPACK_IMPORTED_MODULE_73__["skip"]; }); -/* harmony import */ var _internal_operators_skipLast__WEBPACK_IMPORTED_MODULE_74__ = __webpack_require__(368); +/* harmony import */ var _internal_operators_skipLast__WEBPACK_IMPORTED_MODULE_74__ = __webpack_require__(448); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "skipLast", function() { return _internal_operators_skipLast__WEBPACK_IMPORTED_MODULE_74__["skipLast"]; }); -/* harmony import */ var _internal_operators_skipUntil__WEBPACK_IMPORTED_MODULE_75__ = __webpack_require__(369); +/* harmony import */ var _internal_operators_skipUntil__WEBPACK_IMPORTED_MODULE_75__ = __webpack_require__(449); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "skipUntil", function() { return _internal_operators_skipUntil__WEBPACK_IMPORTED_MODULE_75__["skipUntil"]; }); -/* harmony import */ var _internal_operators_skipWhile__WEBPACK_IMPORTED_MODULE_76__ = __webpack_require__(370); +/* harmony import */ var _internal_operators_skipWhile__WEBPACK_IMPORTED_MODULE_76__ = __webpack_require__(450); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "skipWhile", function() { return _internal_operators_skipWhile__WEBPACK_IMPORTED_MODULE_76__["skipWhile"]; }); -/* harmony import */ var _internal_operators_startWith__WEBPACK_IMPORTED_MODULE_77__ = __webpack_require__(371); +/* harmony import */ var _internal_operators_startWith__WEBPACK_IMPORTED_MODULE_77__ = __webpack_require__(451); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "startWith", function() { return _internal_operators_startWith__WEBPACK_IMPORTED_MODULE_77__["startWith"]; }); -/* harmony import */ var _internal_operators_subscribeOn__WEBPACK_IMPORTED_MODULE_78__ = __webpack_require__(372); +/* harmony import */ var _internal_operators_subscribeOn__WEBPACK_IMPORTED_MODULE_78__ = __webpack_require__(452); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "subscribeOn", function() { return _internal_operators_subscribeOn__WEBPACK_IMPORTED_MODULE_78__["subscribeOn"]; }); -/* harmony import */ var _internal_operators_switchAll__WEBPACK_IMPORTED_MODULE_79__ = __webpack_require__(374); +/* harmony import */ var _internal_operators_switchAll__WEBPACK_IMPORTED_MODULE_79__ = __webpack_require__(454); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "switchAll", function() { return _internal_operators_switchAll__WEBPACK_IMPORTED_MODULE_79__["switchAll"]; }); -/* harmony import */ var _internal_operators_switchMap__WEBPACK_IMPORTED_MODULE_80__ = __webpack_require__(375); +/* harmony import */ var _internal_operators_switchMap__WEBPACK_IMPORTED_MODULE_80__ = __webpack_require__(455); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "switchMap", function() { return _internal_operators_switchMap__WEBPACK_IMPORTED_MODULE_80__["switchMap"]; }); -/* harmony import */ var _internal_operators_switchMapTo__WEBPACK_IMPORTED_MODULE_81__ = __webpack_require__(376); +/* harmony import */ var _internal_operators_switchMapTo__WEBPACK_IMPORTED_MODULE_81__ = __webpack_require__(456); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "switchMapTo", function() { return _internal_operators_switchMapTo__WEBPACK_IMPORTED_MODULE_81__["switchMapTo"]; }); -/* harmony import */ var _internal_operators_take__WEBPACK_IMPORTED_MODULE_82__ = __webpack_require__(323); +/* harmony import */ var _internal_operators_take__WEBPACK_IMPORTED_MODULE_82__ = __webpack_require__(403); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "take", function() { return _internal_operators_take__WEBPACK_IMPORTED_MODULE_82__["take"]; }); -/* harmony import */ var _internal_operators_takeLast__WEBPACK_IMPORTED_MODULE_83__ = __webpack_require__(336); +/* harmony import */ var _internal_operators_takeLast__WEBPACK_IMPORTED_MODULE_83__ = __webpack_require__(416); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "takeLast", function() { return _internal_operators_takeLast__WEBPACK_IMPORTED_MODULE_83__["takeLast"]; }); -/* harmony import */ var _internal_operators_takeUntil__WEBPACK_IMPORTED_MODULE_84__ = __webpack_require__(377); +/* harmony import */ var _internal_operators_takeUntil__WEBPACK_IMPORTED_MODULE_84__ = __webpack_require__(457); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "takeUntil", function() { return _internal_operators_takeUntil__WEBPACK_IMPORTED_MODULE_84__["takeUntil"]; }); -/* harmony import */ var _internal_operators_takeWhile__WEBPACK_IMPORTED_MODULE_85__ = __webpack_require__(378); +/* harmony import */ var _internal_operators_takeWhile__WEBPACK_IMPORTED_MODULE_85__ = __webpack_require__(458); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "takeWhile", function() { return _internal_operators_takeWhile__WEBPACK_IMPORTED_MODULE_85__["takeWhile"]; }); -/* harmony import */ var _internal_operators_tap__WEBPACK_IMPORTED_MODULE_86__ = __webpack_require__(322); +/* harmony import */ var _internal_operators_tap__WEBPACK_IMPORTED_MODULE_86__ = __webpack_require__(402); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "tap", function() { return _internal_operators_tap__WEBPACK_IMPORTED_MODULE_86__["tap"]; }); -/* harmony import */ var _internal_operators_throttle__WEBPACK_IMPORTED_MODULE_87__ = __webpack_require__(379); +/* harmony import */ var _internal_operators_throttle__WEBPACK_IMPORTED_MODULE_87__ = __webpack_require__(459); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "throttle", function() { return _internal_operators_throttle__WEBPACK_IMPORTED_MODULE_87__["throttle"]; }); -/* harmony import */ var _internal_operators_throttleTime__WEBPACK_IMPORTED_MODULE_88__ = __webpack_require__(380); +/* harmony import */ var _internal_operators_throttleTime__WEBPACK_IMPORTED_MODULE_88__ = __webpack_require__(460); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "throttleTime", function() { return _internal_operators_throttleTime__WEBPACK_IMPORTED_MODULE_88__["throttleTime"]; }); -/* harmony import */ var _internal_operators_throwIfEmpty__WEBPACK_IMPORTED_MODULE_89__ = __webpack_require__(321); +/* harmony import */ var _internal_operators_throwIfEmpty__WEBPACK_IMPORTED_MODULE_89__ = __webpack_require__(401); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "throwIfEmpty", function() { return _internal_operators_throwIfEmpty__WEBPACK_IMPORTED_MODULE_89__["throwIfEmpty"]; }); -/* harmony import */ var _internal_operators_timeInterval__WEBPACK_IMPORTED_MODULE_90__ = __webpack_require__(381); +/* harmony import */ var _internal_operators_timeInterval__WEBPACK_IMPORTED_MODULE_90__ = __webpack_require__(461); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "timeInterval", function() { return _internal_operators_timeInterval__WEBPACK_IMPORTED_MODULE_90__["timeInterval"]; }); -/* harmony import */ var _internal_operators_timeout__WEBPACK_IMPORTED_MODULE_91__ = __webpack_require__(382); +/* harmony import */ var _internal_operators_timeout__WEBPACK_IMPORTED_MODULE_91__ = __webpack_require__(462); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "timeout", function() { return _internal_operators_timeout__WEBPACK_IMPORTED_MODULE_91__["timeout"]; }); -/* harmony import */ var _internal_operators_timeoutWith__WEBPACK_IMPORTED_MODULE_92__ = __webpack_require__(383); +/* harmony import */ var _internal_operators_timeoutWith__WEBPACK_IMPORTED_MODULE_92__ = __webpack_require__(463); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "timeoutWith", function() { return _internal_operators_timeoutWith__WEBPACK_IMPORTED_MODULE_92__["timeoutWith"]; }); -/* harmony import */ var _internal_operators_timestamp__WEBPACK_IMPORTED_MODULE_93__ = __webpack_require__(384); +/* harmony import */ var _internal_operators_timestamp__WEBPACK_IMPORTED_MODULE_93__ = __webpack_require__(464); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "timestamp", function() { return _internal_operators_timestamp__WEBPACK_IMPORTED_MODULE_93__["timestamp"]; }); -/* harmony import */ var _internal_operators_toArray__WEBPACK_IMPORTED_MODULE_94__ = __webpack_require__(385); +/* harmony import */ var _internal_operators_toArray__WEBPACK_IMPORTED_MODULE_94__ = __webpack_require__(465); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "toArray", function() { return _internal_operators_toArray__WEBPACK_IMPORTED_MODULE_94__["toArray"]; }); -/* harmony import */ var _internal_operators_window__WEBPACK_IMPORTED_MODULE_95__ = __webpack_require__(386); +/* harmony import */ var _internal_operators_window__WEBPACK_IMPORTED_MODULE_95__ = __webpack_require__(466); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "window", function() { return _internal_operators_window__WEBPACK_IMPORTED_MODULE_95__["window"]; }); -/* harmony import */ var _internal_operators_windowCount__WEBPACK_IMPORTED_MODULE_96__ = __webpack_require__(387); +/* harmony import */ var _internal_operators_windowCount__WEBPACK_IMPORTED_MODULE_96__ = __webpack_require__(467); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "windowCount", function() { return _internal_operators_windowCount__WEBPACK_IMPORTED_MODULE_96__["windowCount"]; }); -/* harmony import */ var _internal_operators_windowTime__WEBPACK_IMPORTED_MODULE_97__ = __webpack_require__(388); +/* harmony import */ var _internal_operators_windowTime__WEBPACK_IMPORTED_MODULE_97__ = __webpack_require__(468); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "windowTime", function() { return _internal_operators_windowTime__WEBPACK_IMPORTED_MODULE_97__["windowTime"]; }); -/* harmony import */ var _internal_operators_windowToggle__WEBPACK_IMPORTED_MODULE_98__ = __webpack_require__(389); +/* harmony import */ var _internal_operators_windowToggle__WEBPACK_IMPORTED_MODULE_98__ = __webpack_require__(469); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "windowToggle", function() { return _internal_operators_windowToggle__WEBPACK_IMPORTED_MODULE_98__["windowToggle"]; }); -/* harmony import */ var _internal_operators_windowWhen__WEBPACK_IMPORTED_MODULE_99__ = __webpack_require__(390); +/* harmony import */ var _internal_operators_windowWhen__WEBPACK_IMPORTED_MODULE_99__ = __webpack_require__(470); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "windowWhen", function() { return _internal_operators_windowWhen__WEBPACK_IMPORTED_MODULE_99__["windowWhen"]; }); -/* harmony import */ var _internal_operators_withLatestFrom__WEBPACK_IMPORTED_MODULE_100__ = __webpack_require__(391); +/* harmony import */ var _internal_operators_withLatestFrom__WEBPACK_IMPORTED_MODULE_100__ = __webpack_require__(471); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "withLatestFrom", function() { return _internal_operators_withLatestFrom__WEBPACK_IMPORTED_MODULE_100__["withLatestFrom"]; }); -/* harmony import */ var _internal_operators_zip__WEBPACK_IMPORTED_MODULE_101__ = __webpack_require__(392); +/* harmony import */ var _internal_operators_zip__WEBPACK_IMPORTED_MODULE_101__ = __webpack_require__(472); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "zip", function() { return _internal_operators_zip__WEBPACK_IMPORTED_MODULE_101__["zip"]; }); -/* harmony import */ var _internal_operators_zipAll__WEBPACK_IMPORTED_MODULE_102__ = __webpack_require__(393); +/* harmony import */ var _internal_operators_zipAll__WEBPACK_IMPORTED_MODULE_102__ = __webpack_require__(473); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "zipAll", function() { return _internal_operators_zipAll__WEBPACK_IMPORTED_MODULE_102__["zipAll"]; }); /** PURE_IMPORTS_START PURE_IMPORTS_END */ @@ -32358,17 +39206,17 @@ __webpack_require__.r(__webpack_exports__); /***/ }), -/* 295 */ +/* 375 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "audit", function() { return audit; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _util_tryCatch__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(207); -/* harmony import */ var _util_errorObject__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(208); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(258); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _util_tryCatch__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(287); +/* harmony import */ var _util_errorObject__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(288); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(337); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(338); /** PURE_IMPORTS_START tslib,_util_tryCatch,_util_errorObject,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ @@ -32441,15 +39289,15 @@ var AuditSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 296 */ +/* 376 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "auditTime", function() { return auditTime; }); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(243); -/* harmony import */ var _audit__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(295); -/* harmony import */ var _observable_timer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(291); +/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(323); +/* harmony import */ var _audit__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(375); +/* harmony import */ var _observable_timer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(371); /** PURE_IMPORTS_START _scheduler_async,_audit,_observable_timer PURE_IMPORTS_END */ @@ -32464,15 +39312,15 @@ function auditTime(duration, scheduler) { /***/ }), -/* 297 */ +/* 377 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "buffer", function() { return buffer; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(258); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(337); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(338); /** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ @@ -32513,14 +39361,14 @@ var BufferSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 298 */ +/* 378 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "bufferCount", function() { return bufferCount; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); /** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ @@ -32614,16 +39462,16 @@ var BufferSkipCountSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 299 */ +/* 379 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "bufferTime", function() { return bufferTime; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(243); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(198); -/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(233); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(323); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(278); +/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(313); /** PURE_IMPORTS_START tslib,_scheduler_async,_Subscriber,_util_isScheduler PURE_IMPORTS_END */ @@ -32775,16 +39623,16 @@ function dispatchBufferClose(arg) { /***/ }), -/* 300 */ +/* 380 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "bufferToggle", function() { return bufferToggle; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(204); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(258); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(257); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(284); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(338); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(337); /** PURE_IMPORTS_START tslib,_Subscription,_util_subscribeToResult,_OuterSubscriber PURE_IMPORTS_END */ @@ -32895,18 +39743,18 @@ var BufferToggleSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 301 */ +/* 381 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "bufferWhen", function() { return bufferWhen; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(204); -/* harmony import */ var _util_tryCatch__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(207); -/* harmony import */ var _util_errorObject__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(208); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(258); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(284); +/* harmony import */ var _util_tryCatch__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(287); +/* harmony import */ var _util_errorObject__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(288); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(337); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(338); /** PURE_IMPORTS_START tslib,_Subscription,_util_tryCatch,_util_errorObject,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ @@ -32992,15 +39840,15 @@ var BufferWhenSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 302 */ +/* 382 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "catchError", function() { return catchError; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(258); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(337); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(338); /** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ @@ -33049,13 +39897,13 @@ var CatchSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 303 */ +/* 383 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "combineAll", function() { return combineAll; }); -/* harmony import */ var _observable_combineLatest__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(256); +/* harmony import */ var _observable_combineLatest__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(336); /** PURE_IMPORTS_START _observable_combineLatest PURE_IMPORTS_END */ function combineAll(project) { @@ -33065,15 +39913,15 @@ function combineAll(project) { /***/ }), -/* 304 */ +/* 384 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "combineLatest", function() { return combineLatest; }); -/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(205); -/* harmony import */ var _observable_combineLatest__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(256); -/* harmony import */ var _observable_from__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(268); +/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(285); +/* harmony import */ var _observable_combineLatest__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(336); +/* harmony import */ var _observable_from__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(348); /** PURE_IMPORTS_START _util_isArray,_observable_combineLatest,_observable_from PURE_IMPORTS_END */ @@ -33097,13 +39945,13 @@ function combineLatest() { /***/ }), -/* 305 */ +/* 385 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "concat", function() { return concat; }); -/* harmony import */ var _observable_concat__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(267); +/* harmony import */ var _observable_concat__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(347); /** PURE_IMPORTS_START _observable_concat PURE_IMPORTS_END */ function concat() { @@ -33117,13 +39965,13 @@ function concat() { /***/ }), -/* 306 */ +/* 386 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "concatMap", function() { return concatMap; }); -/* harmony import */ var _mergeMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); +/* harmony import */ var _mergeMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(356); /** PURE_IMPORTS_START _mergeMap PURE_IMPORTS_END */ function concatMap(project, resultSelector) { @@ -33133,13 +39981,13 @@ function concatMap(project, resultSelector) { /***/ }), -/* 307 */ +/* 387 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "concatMapTo", function() { return concatMapTo; }); -/* harmony import */ var _concatMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(306); +/* harmony import */ var _concatMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(386); /** PURE_IMPORTS_START _concatMap PURE_IMPORTS_END */ function concatMapTo(innerObservable, resultSelector) { @@ -33149,14 +39997,14 @@ function concatMapTo(innerObservable, resultSelector) { /***/ }), -/* 308 */ +/* 388 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "count", function() { return count; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); /** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ @@ -33214,15 +40062,15 @@ var CountSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 309 */ +/* 389 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "debounce", function() { return debounce; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(258); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(337); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(338); /** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ @@ -33302,15 +40150,15 @@ var DebounceSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 310 */ +/* 390 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "debounceTime", function() { return debounceTime; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(243); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); +/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(323); /** PURE_IMPORTS_START tslib,_Subscriber,_scheduler_async PURE_IMPORTS_END */ @@ -33378,14 +40226,14 @@ function dispatchNext(subscriber) { /***/ }), -/* 311 */ +/* 391 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "defaultIfEmpty", function() { return defaultIfEmpty; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); /** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ @@ -33428,17 +40276,17 @@ var DefaultIfEmptySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 312 */ +/* 392 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "delay", function() { return delay; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(243); -/* harmony import */ var _util_isDate__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(313); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(198); -/* harmony import */ var _Notification__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(230); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(323); +/* harmony import */ var _util_isDate__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(393); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(278); +/* harmony import */ var _Notification__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(310); /** PURE_IMPORTS_START tslib,_scheduler_async,_util_isDate,_Subscriber,_Notification PURE_IMPORTS_END */ @@ -33532,7 +40380,7 @@ var DelayMessage = /*@__PURE__*/ (function () { /***/ }), -/* 313 */ +/* 393 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -33546,17 +40394,17 @@ function isDate(value) { /***/ }), -/* 314 */ +/* 394 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "delayWhen", function() { return delayWhen; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(196); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(258); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(276); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(337); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(338); /** PURE_IMPORTS_START tslib,_Subscriber,_Observable,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ @@ -33687,14 +40535,14 @@ var SubscriptionDelaySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 315 */ +/* 395 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "dematerialize", function() { return dematerialize; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); /** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ @@ -33725,16 +40573,16 @@ var DeMaterializeSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 316 */ +/* 396 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "distinct", function() { return distinct; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "DistinctSubscriber", function() { return DistinctSubscriber; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(258); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(337); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(338); /** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ @@ -33803,16 +40651,16 @@ var DistinctSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 317 */ +/* 397 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "distinctUntilChanged", function() { return distinctUntilChanged; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); -/* harmony import */ var _util_tryCatch__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(207); -/* harmony import */ var _util_errorObject__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(208); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); +/* harmony import */ var _util_tryCatch__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(287); +/* harmony import */ var _util_errorObject__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(288); /** PURE_IMPORTS_START tslib,_Subscriber,_util_tryCatch,_util_errorObject PURE_IMPORTS_END */ @@ -33875,13 +40723,13 @@ var DistinctUntilChangedSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 318 */ +/* 398 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "distinctUntilKeyChanged", function() { return distinctUntilKeyChanged; }); -/* harmony import */ var _distinctUntilChanged__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(317); +/* harmony import */ var _distinctUntilChanged__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(397); /** PURE_IMPORTS_START _distinctUntilChanged PURE_IMPORTS_END */ function distinctUntilKeyChanged(key, compare) { @@ -33891,17 +40739,17 @@ function distinctUntilKeyChanged(key, compare) { /***/ }), -/* 319 */ +/* 399 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "elementAt", function() { return elementAt; }); -/* harmony import */ var _util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(250); -/* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(320); -/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(321); -/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(311); -/* harmony import */ var _take__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(323); +/* harmony import */ var _util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(330); +/* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(400); +/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(401); +/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(391); +/* harmony import */ var _take__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(403); /** PURE_IMPORTS_START _util_ArgumentOutOfRangeError,_filter,_throwIfEmpty,_defaultIfEmpty,_take PURE_IMPORTS_END */ @@ -33923,14 +40771,14 @@ function elementAt(index, defaultValue) { /***/ }), -/* 320 */ +/* 400 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "filter", function() { return filter; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); /** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ @@ -33977,14 +40825,14 @@ var FilterSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 321 */ +/* 401 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "throwIfEmpty", function() { return throwIfEmpty; }); -/* harmony import */ var _tap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(322); -/* harmony import */ var _util_EmptyError__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(251); +/* harmony import */ var _tap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(402); +/* harmony import */ var _util_EmptyError__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(331); /** PURE_IMPORTS_START _tap,_util_EmptyError PURE_IMPORTS_END */ @@ -34009,16 +40857,16 @@ function defaultErrorFactory() { /***/ }), -/* 322 */ +/* 402 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "tap", function() { return tap; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); -/* harmony import */ var _util_noop__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(213); -/* harmony import */ var _util_isFunction__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(200); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); +/* harmony import */ var _util_noop__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(293); +/* harmony import */ var _util_isFunction__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(280); /** PURE_IMPORTS_START tslib,_Subscriber,_util_noop,_util_isFunction PURE_IMPORTS_END */ @@ -34097,16 +40945,16 @@ var TapSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 323 */ +/* 403 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "take", function() { return take; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); -/* harmony import */ var _util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(250); -/* harmony import */ var _observable_empty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(231); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); +/* harmony import */ var _util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(330); +/* harmony import */ var _observable_empty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(311); /** PURE_IMPORTS_START tslib,_Subscriber,_util_ArgumentOutOfRangeError,_observable_empty PURE_IMPORTS_END */ @@ -34159,17 +41007,17 @@ var TakeSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 324 */ +/* 404 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "endWith", function() { return endWith; }); -/* harmony import */ var _observable_fromArray__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(234); -/* harmony import */ var _observable_scalar__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(236); -/* harmony import */ var _observable_empty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(231); -/* harmony import */ var _observable_concat__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(267); -/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(233); +/* harmony import */ var _observable_fromArray__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(314); +/* harmony import */ var _observable_scalar__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(316); +/* harmony import */ var _observable_empty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(311); +/* harmony import */ var _observable_concat__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(347); +/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(313); /** PURE_IMPORTS_START _observable_fromArray,_observable_scalar,_observable_empty,_observable_concat,_util_isScheduler PURE_IMPORTS_END */ @@ -34205,14 +41053,14 @@ function endWith() { /***/ }), -/* 325 */ +/* 405 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "every", function() { return every; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); /** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ @@ -34267,15 +41115,15 @@ var EverySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 326 */ +/* 406 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "exhaust", function() { return exhaust; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(258); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(337); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(338); /** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ @@ -34324,17 +41172,17 @@ var SwitchFirstSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 327 */ +/* 407 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "exhaustMap", function() { return exhaustMap; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(258); -/* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(254); -/* harmony import */ var _observable_from__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(268); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(337); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(338); +/* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(334); +/* harmony import */ var _observable_from__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(348); /** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult,_map,_observable_from PURE_IMPORTS_END */ @@ -34410,7 +41258,7 @@ var ExhaustMapSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 328 */ +/* 408 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -34418,11 +41266,11 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "expand", function() { return expand; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ExpandOperator", function() { return ExpandOperator; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ExpandSubscriber", function() { return ExpandSubscriber; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _util_tryCatch__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(207); -/* harmony import */ var _util_errorObject__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(208); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(258); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _util_tryCatch__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(287); +/* harmony import */ var _util_errorObject__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(288); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(337); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(338); /** PURE_IMPORTS_START tslib,_util_tryCatch,_util_errorObject,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ @@ -34526,15 +41374,15 @@ var ExpandSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 329 */ +/* 409 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "finalize", function() { return finalize; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(204); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(284); /** PURE_IMPORTS_START tslib,_Subscriber,_Subscription PURE_IMPORTS_END */ @@ -34564,7 +41412,7 @@ var FinallySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 330 */ +/* 410 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -34572,8 +41420,8 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "find", function() { return find; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "FindValueOperator", function() { return FindValueOperator; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "FindValueSubscriber", function() { return FindValueSubscriber; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); /** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ @@ -34635,13 +41483,13 @@ var FindValueSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 331 */ +/* 411 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "findIndex", function() { return findIndex; }); -/* harmony import */ var _operators_find__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(330); +/* harmony import */ var _operators_find__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(410); /** PURE_IMPORTS_START _operators_find PURE_IMPORTS_END */ function findIndex(predicate, thisArg) { @@ -34651,18 +41499,18 @@ function findIndex(predicate, thisArg) { /***/ }), -/* 332 */ +/* 412 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "first", function() { return first; }); -/* harmony import */ var _util_EmptyError__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(251); -/* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(320); -/* harmony import */ var _take__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(323); -/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(311); -/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(321); -/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(248); +/* harmony import */ var _util_EmptyError__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(331); +/* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(400); +/* harmony import */ var _take__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(403); +/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(391); +/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(401); +/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(328); /** PURE_IMPORTS_START _util_EmptyError,_filter,_take,_defaultIfEmpty,_throwIfEmpty,_util_identity PURE_IMPORTS_END */ @@ -34678,14 +41526,14 @@ function first(predicate, defaultValue) { /***/ }), -/* 333 */ +/* 413 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ignoreElements", function() { return ignoreElements; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); /** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ @@ -34715,14 +41563,14 @@ var IgnoreElementsSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 334 */ +/* 414 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isEmpty", function() { return isEmpty; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); /** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ @@ -34759,18 +41607,18 @@ var IsEmptySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 335 */ +/* 415 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "last", function() { return last; }); -/* harmony import */ var _util_EmptyError__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(251); -/* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(320); -/* harmony import */ var _takeLast__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(336); -/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(321); -/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(311); -/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(248); +/* harmony import */ var _util_EmptyError__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(331); +/* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(400); +/* harmony import */ var _takeLast__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(416); +/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(401); +/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(391); +/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(328); /** PURE_IMPORTS_START _util_EmptyError,_filter,_takeLast,_throwIfEmpty,_defaultIfEmpty,_util_identity PURE_IMPORTS_END */ @@ -34786,16 +41634,16 @@ function last(predicate, defaultValue) { /***/ }), -/* 336 */ +/* 416 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "takeLast", function() { return takeLast; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); -/* harmony import */ var _util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(250); -/* harmony import */ var _observable_empty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(231); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); +/* harmony import */ var _util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(330); +/* harmony import */ var _observable_empty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(311); /** PURE_IMPORTS_START tslib,_Subscriber,_util_ArgumentOutOfRangeError,_observable_empty PURE_IMPORTS_END */ @@ -34863,14 +41711,14 @@ var TakeLastSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 337 */ +/* 417 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "mapTo", function() { return mapTo; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); /** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ @@ -34902,15 +41750,15 @@ var MapToSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 338 */ +/* 418 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "materialize", function() { return materialize; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); -/* harmony import */ var _Notification__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(230); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); +/* harmony import */ var _Notification__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(310); /** PURE_IMPORTS_START tslib,_Subscriber,_Notification PURE_IMPORTS_END */ @@ -34952,13 +41800,13 @@ var MaterializeSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 339 */ +/* 419 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "max", function() { return max; }); -/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(340); +/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(420); /** PURE_IMPORTS_START _reduce PURE_IMPORTS_END */ function max(comparer) { @@ -34971,16 +41819,16 @@ function max(comparer) { /***/ }), -/* 340 */ +/* 420 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "reduce", function() { return reduce; }); -/* harmony import */ var _scan__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(341); -/* harmony import */ var _takeLast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(336); -/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(311); -/* harmony import */ var _util_pipe__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(212); +/* harmony import */ var _scan__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(421); +/* harmony import */ var _takeLast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(416); +/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(391); +/* harmony import */ var _util_pipe__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(292); /** PURE_IMPORTS_START _scan,_takeLast,_defaultIfEmpty,_util_pipe PURE_IMPORTS_END */ @@ -35002,14 +41850,14 @@ function reduce(accumulator, seed) { /***/ }), -/* 341 */ +/* 421 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "scan", function() { return scan; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); /** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ @@ -35084,13 +41932,13 @@ var ScanSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 342 */ +/* 422 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "merge", function() { return merge; }); -/* harmony import */ var _observable_merge__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(285); +/* harmony import */ var _observable_merge__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(365); /** PURE_IMPORTS_START _observable_merge PURE_IMPORTS_END */ function merge() { @@ -35104,13 +41952,13 @@ function merge() { /***/ }), -/* 343 */ +/* 423 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "mergeMapTo", function() { return mergeMapTo; }); -/* harmony import */ var _mergeMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); +/* harmony import */ var _mergeMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(356); /** PURE_IMPORTS_START _mergeMap PURE_IMPORTS_END */ function mergeMapTo(innerObservable, resultSelector, concurrent) { @@ -35129,7 +41977,7 @@ function mergeMapTo(innerObservable, resultSelector, concurrent) { /***/ }), -/* 344 */ +/* 424 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -35137,11 +41985,11 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "mergeScan", function() { return mergeScan; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MergeScanOperator", function() { return MergeScanOperator; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MergeScanSubscriber", function() { return MergeScanSubscriber; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _util_tryCatch__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(207); -/* harmony import */ var _util_errorObject__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(208); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(258); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(257); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _util_tryCatch__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(287); +/* harmony import */ var _util_errorObject__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(288); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(338); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(337); /** PURE_IMPORTS_START tslib,_util_tryCatch,_util_errorObject,_util_subscribeToResult,_OuterSubscriber PURE_IMPORTS_END */ @@ -35236,13 +42084,13 @@ var MergeScanSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 345 */ +/* 425 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "min", function() { return min; }); -/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(340); +/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(420); /** PURE_IMPORTS_START _reduce PURE_IMPORTS_END */ function min(comparer) { @@ -35255,14 +42103,14 @@ function min(comparer) { /***/ }), -/* 346 */ +/* 426 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "multicast", function() { return multicast; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MulticastOperator", function() { return MulticastOperator; }); -/* harmony import */ var _observable_ConnectableObservable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(214); +/* harmony import */ var _observable_ConnectableObservable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(294); /** PURE_IMPORTS_START _observable_ConnectableObservable PURE_IMPORTS_END */ function multicast(subjectOrSubjectFactory, selector) { @@ -35304,18 +42152,18 @@ var MulticastOperator = /*@__PURE__*/ (function () { /***/ }), -/* 347 */ +/* 427 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "onErrorResumeNext", function() { return onErrorResumeNext; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "onErrorResumeNextStatic", function() { return onErrorResumeNextStatic; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _observable_from__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(268); -/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(205); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(258); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _observable_from__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(348); +/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(285); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(337); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(338); /** PURE_IMPORTS_START tslib,_observable_from,_util_isArray,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ @@ -35388,14 +42236,14 @@ var OnErrorResumeNextSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 348 */ +/* 428 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "pairwise", function() { return pairwise; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); /** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ @@ -35432,14 +42280,14 @@ var PairwiseSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 349 */ +/* 429 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "partition", function() { return partition; }); -/* harmony import */ var _util_not__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(350); -/* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(320); +/* harmony import */ var _util_not__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(430); +/* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(400); /** PURE_IMPORTS_START _util_not,_filter PURE_IMPORTS_END */ @@ -35455,7 +42303,7 @@ function partition(predicate, thisArg) { /***/ }), -/* 350 */ +/* 430 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -35474,13 +42322,13 @@ function not(pred, thisArg) { /***/ }), -/* 351 */ +/* 431 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "pluck", function() { return pluck; }); -/* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(254); +/* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(334); /** PURE_IMPORTS_START _map PURE_IMPORTS_END */ function pluck() { @@ -35514,14 +42362,14 @@ function plucker(props, length) { /***/ }), -/* 352 */ +/* 432 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "publish", function() { return publish; }); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(215); -/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(346); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(295); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(426); /** PURE_IMPORTS_START _Subject,_multicast PURE_IMPORTS_END */ @@ -35534,14 +42382,14 @@ function publish(selector) { /***/ }), -/* 353 */ +/* 433 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "publishBehavior", function() { return publishBehavior; }); -/* harmony import */ var _BehaviorSubject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(220); -/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(346); +/* harmony import */ var _BehaviorSubject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(300); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(426); /** PURE_IMPORTS_START _BehaviorSubject,_multicast PURE_IMPORTS_END */ @@ -35552,14 +42400,14 @@ function publishBehavior(value) { /***/ }), -/* 354 */ +/* 434 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "publishLast", function() { return publishLast; }); -/* harmony import */ var _AsyncSubject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(238); -/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(346); +/* harmony import */ var _AsyncSubject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(318); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(426); /** PURE_IMPORTS_START _AsyncSubject,_multicast PURE_IMPORTS_END */ @@ -35570,14 +42418,14 @@ function publishLast() { /***/ }), -/* 355 */ +/* 435 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "publishReplay", function() { return publishReplay; }); -/* harmony import */ var _ReplaySubject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(221); -/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(346); +/* harmony import */ var _ReplaySubject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(301); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(426); /** PURE_IMPORTS_START _ReplaySubject,_multicast PURE_IMPORTS_END */ @@ -35593,14 +42441,14 @@ function publishReplay(bufferSize, windowTime, selectorOrScheduler, scheduler) { /***/ }), -/* 356 */ +/* 436 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "race", function() { return race; }); -/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(205); -/* harmony import */ var _observable_race__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(289); +/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(285); +/* harmony import */ var _observable_race__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(369); /** PURE_IMPORTS_START _util_isArray,_observable_race PURE_IMPORTS_END */ @@ -35620,15 +42468,15 @@ function race() { /***/ }), -/* 357 */ +/* 437 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "repeat", function() { return repeat; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); -/* harmony import */ var _observable_empty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(231); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); +/* harmony import */ var _observable_empty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(311); /** PURE_IMPORTS_START tslib,_Subscriber,_observable_empty PURE_IMPORTS_END */ @@ -35685,18 +42533,18 @@ var RepeatSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 358 */ +/* 438 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "repeatWhen", function() { return repeatWhen; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(215); -/* harmony import */ var _util_tryCatch__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(207); -/* harmony import */ var _util_errorObject__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(208); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(258); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(295); +/* harmony import */ var _util_tryCatch__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(287); +/* harmony import */ var _util_errorObject__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(288); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(337); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(338); /** PURE_IMPORTS_START tslib,_Subject,_util_tryCatch,_util_errorObject,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ @@ -35781,14 +42629,14 @@ var RepeatWhenSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 359 */ +/* 439 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "retry", function() { return retry; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); /** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ @@ -35834,18 +42682,18 @@ var RetrySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 360 */ +/* 440 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "retryWhen", function() { return retryWhen; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(215); -/* harmony import */ var _util_tryCatch__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(207); -/* harmony import */ var _util_errorObject__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(208); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(258); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(295); +/* harmony import */ var _util_tryCatch__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(287); +/* harmony import */ var _util_errorObject__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(288); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(337); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(338); /** PURE_IMPORTS_START tslib,_Subject,_util_tryCatch,_util_errorObject,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ @@ -35923,15 +42771,15 @@ var RetryWhenSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 361 */ +/* 441 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "sample", function() { return sample; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(258); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(337); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(338); /** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ @@ -35980,15 +42828,15 @@ var SampleSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 362 */ +/* 442 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "sampleTime", function() { return sampleTime; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(243); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); +/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(323); /** PURE_IMPORTS_START tslib,_Subscriber,_scheduler_async PURE_IMPORTS_END */ @@ -36040,7 +42888,7 @@ function dispatchNotification(state) { /***/ }), -/* 363 */ +/* 443 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -36048,10 +42896,10 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "sequenceEqual", function() { return sequenceEqual; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SequenceEqualOperator", function() { return SequenceEqualOperator; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SequenceEqualSubscriber", function() { return SequenceEqualSubscriber; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); -/* harmony import */ var _util_tryCatch__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(207); -/* harmony import */ var _util_errorObject__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(208); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); +/* harmony import */ var _util_tryCatch__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(287); +/* harmony import */ var _util_errorObject__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(288); /** PURE_IMPORTS_START tslib,_Subscriber,_util_tryCatch,_util_errorObject PURE_IMPORTS_END */ @@ -36159,15 +43007,15 @@ var SequenceEqualCompareToSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 364 */ +/* 444 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "share", function() { return share; }); -/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(346); -/* harmony import */ var _refCount__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(218); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(215); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(426); +/* harmony import */ var _refCount__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(298); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(295); /** PURE_IMPORTS_START _multicast,_refCount,_Subject PURE_IMPORTS_END */ @@ -36182,13 +43030,13 @@ function share() { /***/ }), -/* 365 */ +/* 445 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "shareReplay", function() { return shareReplay; }); -/* harmony import */ var _ReplaySubject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(221); +/* harmony import */ var _ReplaySubject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(301); /** PURE_IMPORTS_START _ReplaySubject PURE_IMPORTS_END */ function shareReplay(bufferSize, windowTime, scheduler) { @@ -36231,15 +43079,15 @@ function shareReplayOperator(bufferSize, windowTime, scheduler) { /***/ }), -/* 366 */ +/* 446 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "single", function() { return single; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); -/* harmony import */ var _util_EmptyError__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(251); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); +/* harmony import */ var _util_EmptyError__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(331); /** PURE_IMPORTS_START tslib,_Subscriber,_util_EmptyError PURE_IMPORTS_END */ @@ -36311,14 +43159,14 @@ var SingleSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 367 */ +/* 447 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "skip", function() { return skip; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); /** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ @@ -36353,15 +43201,15 @@ var SkipSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 368 */ +/* 448 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "skipLast", function() { return skipLast; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); -/* harmony import */ var _util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(250); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); +/* harmony import */ var _util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(330); /** PURE_IMPORTS_START tslib,_Subscriber,_util_ArgumentOutOfRangeError PURE_IMPORTS_END */ @@ -36415,15 +43263,15 @@ var SkipLastSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 369 */ +/* 449 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "skipUntil", function() { return skipUntil; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(258); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(337); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(338); /** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ @@ -36467,14 +43315,14 @@ var SkipUntilSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 370 */ +/* 450 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "skipWhile", function() { return skipWhile; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); /** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ @@ -36523,17 +43371,17 @@ var SkipWhileSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 371 */ +/* 451 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "startWith", function() { return startWith; }); -/* harmony import */ var _observable_fromArray__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(234); -/* harmony import */ var _observable_scalar__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(236); -/* harmony import */ var _observable_empty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(231); -/* harmony import */ var _observable_concat__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(267); -/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(233); +/* harmony import */ var _observable_fromArray__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(314); +/* harmony import */ var _observable_scalar__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(316); +/* harmony import */ var _observable_empty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(311); +/* harmony import */ var _observable_concat__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(347); +/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(313); /** PURE_IMPORTS_START _observable_fromArray,_observable_scalar,_observable_empty,_observable_concat,_util_isScheduler PURE_IMPORTS_END */ @@ -36569,13 +43417,13 @@ function startWith() { /***/ }), -/* 372 */ +/* 452 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "subscribeOn", function() { return subscribeOn; }); -/* harmony import */ var _observable_SubscribeOnObservable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(373); +/* harmony import */ var _observable_SubscribeOnObservable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(453); /** PURE_IMPORTS_START _observable_SubscribeOnObservable PURE_IMPORTS_END */ function subscribeOn(scheduler, delay) { @@ -36600,16 +43448,16 @@ var SubscribeOnOperator = /*@__PURE__*/ (function () { /***/ }), -/* 373 */ +/* 453 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SubscribeOnObservable", function() { return SubscribeOnObservable; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(196); -/* harmony import */ var _scheduler_asap__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(239); -/* harmony import */ var _util_isNumeric__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(284); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(276); +/* harmony import */ var _scheduler_asap__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(319); +/* harmony import */ var _util_isNumeric__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(364); /** PURE_IMPORTS_START tslib,_Observable,_scheduler_asap,_util_isNumeric PURE_IMPORTS_END */ @@ -36664,14 +43512,14 @@ var SubscribeOnObservable = /*@__PURE__*/ (function (_super) { /***/ }), -/* 374 */ +/* 454 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "switchAll", function() { return switchAll; }); -/* harmony import */ var _switchMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(375); -/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(248); +/* harmony import */ var _switchMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(455); +/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(328); /** PURE_IMPORTS_START _switchMap,_util_identity PURE_IMPORTS_END */ @@ -36682,17 +43530,17 @@ function switchAll() { /***/ }), -/* 375 */ +/* 455 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "switchMap", function() { return switchMap; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(258); -/* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(254); -/* harmony import */ var _observable_from__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(268); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(337); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(338); +/* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(334); +/* harmony import */ var _observable_from__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(348); /** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult,_map,_observable_from PURE_IMPORTS_END */ @@ -36766,13 +43614,13 @@ var SwitchMapSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 376 */ +/* 456 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "switchMapTo", function() { return switchMapTo; }); -/* harmony import */ var _switchMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(375); +/* harmony import */ var _switchMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(455); /** PURE_IMPORTS_START _switchMap PURE_IMPORTS_END */ function switchMapTo(innerObservable, resultSelector) { @@ -36782,15 +43630,15 @@ function switchMapTo(innerObservable, resultSelector) { /***/ }), -/* 377 */ +/* 457 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "takeUntil", function() { return takeUntil; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(258); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(337); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(338); /** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ @@ -36829,14 +43677,14 @@ var TakeUntilSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 378 */ +/* 458 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "takeWhile", function() { return takeWhile; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); /** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ @@ -36887,16 +43735,16 @@ var TakeWhileSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 379 */ +/* 459 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "defaultThrottleConfig", function() { return defaultThrottleConfig; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "throttle", function() { return throttle; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(258); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(337); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(338); /** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ @@ -36991,16 +43839,16 @@ var ThrottleSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 380 */ +/* 460 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "throttleTime", function() { return throttleTime; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(243); -/* harmony import */ var _throttle__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(379); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); +/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(323); +/* harmony import */ var _throttle__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(459); /** PURE_IMPORTS_START tslib,_Subscriber,_scheduler_async,_throttle PURE_IMPORTS_END */ @@ -37085,17 +43933,17 @@ function dispatchNext(arg) { /***/ }), -/* 381 */ +/* 461 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "timeInterval", function() { return timeInterval; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TimeInterval", function() { return TimeInterval; }); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(243); -/* harmony import */ var _scan__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(341); -/* harmony import */ var _observable_defer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(277); -/* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(254); +/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(323); +/* harmony import */ var _scan__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(421); +/* harmony import */ var _observable_defer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(357); +/* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(334); /** PURE_IMPORTS_START _scheduler_async,_scan,_observable_defer,_map PURE_IMPORTS_END */ @@ -37129,16 +43977,16 @@ var TimeInterval = /*@__PURE__*/ (function () { /***/ }), -/* 382 */ +/* 462 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "timeout", function() { return timeout; }); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(243); -/* harmony import */ var _util_TimeoutError__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(252); -/* harmony import */ var _timeoutWith__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(383); -/* harmony import */ var _observable_throwError__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(237); +/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(323); +/* harmony import */ var _util_TimeoutError__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(332); +/* harmony import */ var _timeoutWith__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(463); +/* harmony import */ var _observable_throwError__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(317); /** PURE_IMPORTS_START _scheduler_async,_util_TimeoutError,_timeoutWith,_observable_throwError PURE_IMPORTS_END */ @@ -37154,17 +44002,17 @@ function timeout(due, scheduler) { /***/ }), -/* 383 */ +/* 463 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "timeoutWith", function() { return timeoutWith; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(243); -/* harmony import */ var _util_isDate__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(313); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(258); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(323); +/* harmony import */ var _util_isDate__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(393); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(337); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(338); /** PURE_IMPORTS_START tslib,_scheduler_async,_util_isDate,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ @@ -37236,15 +44084,15 @@ var TimeoutWithSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 384 */ +/* 464 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "timestamp", function() { return timestamp; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Timestamp", function() { return Timestamp; }); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(243); -/* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(254); +/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(323); +/* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(334); /** PURE_IMPORTS_START _scheduler_async,_map PURE_IMPORTS_END */ @@ -37266,13 +44114,13 @@ var Timestamp = /*@__PURE__*/ (function () { /***/ }), -/* 385 */ +/* 465 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "toArray", function() { return toArray; }); -/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(340); +/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(420); /** PURE_IMPORTS_START _reduce PURE_IMPORTS_END */ function toArrayReducer(arr, item, index) { @@ -37289,16 +44137,16 @@ function toArray() { /***/ }), -/* 386 */ +/* 466 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "window", function() { return window; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(215); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(258); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(295); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(337); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(338); /** PURE_IMPORTS_START tslib,_Subject,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ @@ -37369,15 +44217,15 @@ var WindowSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 387 */ +/* 467 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "windowCount", function() { return windowCount; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(215); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(295); /** PURE_IMPORTS_START tslib,_Subscriber,_Subject PURE_IMPORTS_END */ @@ -37459,18 +44307,18 @@ var WindowCountSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 388 */ +/* 468 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "windowTime", function() { return windowTime; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(215); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(243); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(198); -/* harmony import */ var _util_isNumeric__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(284); -/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(233); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(295); +/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(323); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(278); +/* harmony import */ var _util_isNumeric__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(364); +/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(313); /** PURE_IMPORTS_START tslib,_Subject,_scheduler_async,_Subscriber,_util_isNumeric,_util_isScheduler PURE_IMPORTS_END */ @@ -37629,19 +44477,19 @@ function dispatchWindowClose(state) { /***/ }), -/* 389 */ +/* 469 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "windowToggle", function() { return windowToggle; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(215); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(204); -/* harmony import */ var _util_tryCatch__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(207); -/* harmony import */ var _util_errorObject__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(208); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(258); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(295); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(284); +/* harmony import */ var _util_tryCatch__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(287); +/* harmony import */ var _util_errorObject__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(288); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(337); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(338); /** PURE_IMPORTS_START tslib,_Subject,_Subscription,_util_tryCatch,_util_errorObject,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ @@ -37775,18 +44623,18 @@ var WindowToggleSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 390 */ +/* 470 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "windowWhen", function() { return windowWhen; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(215); -/* harmony import */ var _util_tryCatch__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(207); -/* harmony import */ var _util_errorObject__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(208); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(258); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(295); +/* harmony import */ var _util_tryCatch__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(287); +/* harmony import */ var _util_errorObject__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(288); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(337); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(338); /** PURE_IMPORTS_START tslib,_Subject,_util_tryCatch,_util_errorObject,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ @@ -37874,15 +44722,15 @@ var WindowSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 391 */ +/* 471 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "withLatestFrom", function() { return withLatestFrom; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(257); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(258); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(337); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(338); /** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ @@ -37969,13 +44817,13 @@ var WithLatestFromSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 392 */ +/* 472 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "zip", function() { return zip; }); -/* harmony import */ var _observable_zip__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(293); +/* harmony import */ var _observable_zip__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(373); /** PURE_IMPORTS_START _observable_zip PURE_IMPORTS_END */ function zip() { @@ -37991,13 +44839,13 @@ function zip() { /***/ }), -/* 393 */ +/* 473 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "zipAll", function() { return zipAll; }); -/* harmony import */ var _observable_zip__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(293); +/* harmony import */ var _observable_zip__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(373); /** PURE_IMPORTS_START _observable_zip PURE_IMPORTS_END */ function zipAll(project) { @@ -38007,7 +44855,7 @@ function zipAll(project) { /***/ }), -/* 394 */ +/* 474 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -38015,15 +44863,15 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "runCommand", function() { return runCommand; }); /* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2); /* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(chalk__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var indent_string__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(395); +/* harmony import */ var indent_string__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(259); /* harmony import */ var indent_string__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(indent_string__WEBPACK_IMPORTED_MODULE_1__); -/* harmony import */ var wrap_ansi__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(396); +/* harmony import */ var wrap_ansi__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(475); /* harmony import */ var wrap_ansi__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(wrap_ansi__WEBPACK_IMPORTED_MODULE_2__); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(164); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(172); /* harmony import */ var _utils_errors__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(53); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(34); /* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(36); -/* harmony import */ var _utils_projects_tree__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(403); +/* harmony import */ var _utils_projects_tree__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(482); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -38101,47 +44949,13 @@ function toArray(value) { } /***/ }), -/* 395 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -module.exports = (str, count, opts) => { - // Support older versions: use the third parameter as options.indent - // TODO: Remove the workaround in the next major version - const options = typeof opts === 'object' ? Object.assign({indent: ' '}, opts) : {indent: opts || ' '}; - count = count === undefined ? 1 : count; - - if (typeof str !== 'string') { - throw new TypeError(`Expected \`input\` to be a \`string\`, got \`${typeof str}\``); - } - - if (typeof count !== 'number') { - throw new TypeError(`Expected \`count\` to be a \`number\`, got \`${typeof count}\``); - } - - if (typeof options.indent !== 'string') { - throw new TypeError(`Expected \`options.indent\` to be a \`string\`, got \`${typeof options.indent}\``); - } - - if (count === 0) { - return str; - } - - const regex = options.includeEmptyLines ? /^/mg : /^(?!\s*$)/mg; - return str.replace(regex, options.indent.repeat(count)); -} -; - - -/***/ }), -/* 396 */ +/* 475 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const stringWidth = __webpack_require__(397); -const stripAnsi = __webpack_require__(401); +const stringWidth = __webpack_require__(476); +const stripAnsi = __webpack_require__(480); const ESCAPES = new Set([ '\u001B', @@ -38335,13 +45149,13 @@ module.exports = (str, cols, opts) => { /***/ }), -/* 397 */ +/* 476 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const stripAnsi = __webpack_require__(398); -const isFullwidthCodePoint = __webpack_require__(400); +const stripAnsi = __webpack_require__(477); +const isFullwidthCodePoint = __webpack_require__(479); module.exports = str => { if (typeof str !== 'string' || str.length === 0) { @@ -38378,18 +45192,18 @@ module.exports = str => { /***/ }), -/* 398 */ +/* 477 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const ansiRegex = __webpack_require__(399); +const ansiRegex = __webpack_require__(478); module.exports = input => typeof input === 'string' ? input.replace(ansiRegex(), '') : input; /***/ }), -/* 399 */ +/* 478 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -38406,7 +45220,7 @@ module.exports = () => { /***/ }), -/* 400 */ +/* 479 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -38459,18 +45273,18 @@ module.exports = x => { /***/ }), -/* 401 */ +/* 480 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const ansiRegex = __webpack_require__(402); +const ansiRegex = __webpack_require__(481); module.exports = input => typeof input === 'string' ? input.replace(ansiRegex(), '') : input; /***/ }), -/* 402 */ +/* 481 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -38487,7 +45301,7 @@ module.exports = () => { /***/ }), -/* 403 */ +/* 482 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -38640,15 +45454,15 @@ function addProjectToTree(tree, pathParts, project) { } /***/ }), -/* 404 */ +/* 483 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); -/* harmony import */ var _build_production_projects__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(405); +/* harmony import */ var _build_production_projects__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(484); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildProductionProjects", function() { return _build_production_projects__WEBPACK_IMPORTED_MODULE_0__["buildProductionProjects"]; }); -/* harmony import */ var _prepare_project_dependencies__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(612); +/* harmony import */ var _prepare_project_dependencies__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(691); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "prepareExternalProjectDependencies", function() { return _prepare_project_dependencies__WEBPACK_IMPORTED_MODULE_1__["prepareExternalProjectDependencies"]; }); /* @@ -38673,19 +45487,19 @@ __webpack_require__.r(__webpack_exports__); /***/ }), -/* 405 */ +/* 484 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "buildProductionProjects", function() { return buildProductionProjects; }); -/* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(406); +/* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(485); /* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(cpy__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(166); +/* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(174); /* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(del__WEBPACK_IMPORTED_MODULE_1__); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(16); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_2__); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(164); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(172); /* harmony import */ var _utils_fs__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(20); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(34); /* harmony import */ var _utils_package_json__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(55); @@ -38819,17 +45633,17 @@ async function copyToBuild(project, kibanaRoot, buildRoot) { } /***/ }), -/* 406 */ +/* 485 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const EventEmitter = __webpack_require__(46); const path = __webpack_require__(16); -const arrify = __webpack_require__(407); -const globby = __webpack_require__(408); -const cpFile = __webpack_require__(602); -const CpyError = __webpack_require__(610); +const arrify = __webpack_require__(486); +const globby = __webpack_require__(487); +const cpFile = __webpack_require__(681); +const CpyError = __webpack_require__(689); const preprocessSrcPath = (srcPath, options) => options.cwd ? path.resolve(options.cwd, srcPath) : srcPath; @@ -38934,7 +45748,7 @@ module.exports.default = cpy; /***/ }), -/* 407 */ +/* 486 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -38949,17 +45763,17 @@ module.exports = function (val) { /***/ }), -/* 408 */ +/* 487 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(23); -const arrayUnion = __webpack_require__(170); +const arrayUnion = __webpack_require__(488); const glob = __webpack_require__(37); -const fastGlob = __webpack_require__(409); -const dirGlob = __webpack_require__(595); -const gitignore = __webpack_require__(598); +const fastGlob = __webpack_require__(490); +const dirGlob = __webpack_require__(674); +const gitignore = __webpack_require__(677); const DEFAULT_FILTER = () => false; @@ -39104,10 +45918,92 @@ module.exports.gitignore = gitignore; /***/ }), -/* 409 */ +/* 488 */ /***/ (function(module, exports, __webpack_require__) { -const pkg = __webpack_require__(410); +"use strict"; + +var arrayUniq = __webpack_require__(489); + +module.exports = function () { + return arrayUniq([].concat.apply([], arguments)); +}; + + +/***/ }), +/* 489 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +// there's 3 implementations written in increasing order of efficiency + +// 1 - no Set type is defined +function uniqNoSet(arr) { + var ret = []; + + for (var i = 0; i < arr.length; i++) { + if (ret.indexOf(arr[i]) === -1) { + ret.push(arr[i]); + } + } + + return ret; +} + +// 2 - a simple Set type is defined +function uniqSet(arr) { + var seen = new Set(); + return arr.filter(function (el) { + if (!seen.has(el)) { + seen.add(el); + return true; + } + + return false; + }); +} + +// 3 - a standard Set type is defined and it has a forEach method +function uniqSetWithForEach(arr) { + var ret = []; + + (new Set(arr)).forEach(function (el) { + ret.push(el); + }); + + return ret; +} + +// V8 currently has a broken implementation +// https://github.com/joyent/node/issues/8449 +function doesForEachActuallyWork() { + var ret = false; + + (new Set([true])).forEach(function (el) { + ret = el; + }); + + return ret === true; +} + +if ('Set' in global) { + if (typeof Set.prototype.forEach === 'function' && doesForEachActuallyWork()) { + module.exports = uniqSetWithForEach; + } else { + module.exports = uniqSet; + } +} else { + module.exports = uniqNoSet; +} + + +/***/ }), +/* 490 */ +/***/ (function(module, exports, __webpack_require__) { + +const pkg = __webpack_require__(491); module.exports = pkg.async; module.exports.default = pkg.async; @@ -39120,19 +46016,19 @@ module.exports.generateTasks = pkg.generateTasks; /***/ }), -/* 410 */ +/* 491 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var optionsManager = __webpack_require__(411); -var taskManager = __webpack_require__(412); -var reader_async_1 = __webpack_require__(565); -var reader_stream_1 = __webpack_require__(589); -var reader_sync_1 = __webpack_require__(590); -var arrayUtils = __webpack_require__(592); -var streamUtils = __webpack_require__(593); +var optionsManager = __webpack_require__(492); +var taskManager = __webpack_require__(493); +var reader_async_1 = __webpack_require__(645); +var reader_stream_1 = __webpack_require__(669); +var reader_sync_1 = __webpack_require__(670); +var arrayUtils = __webpack_require__(672); +var streamUtils = __webpack_require__(673); /** * Synchronous API. */ @@ -39198,7 +46094,7 @@ function isString(source) { /***/ }), -/* 411 */ +/* 492 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -39236,13 +46132,13 @@ exports.prepare = prepare; /***/ }), -/* 412 */ +/* 493 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var patternUtils = __webpack_require__(413); +var patternUtils = __webpack_require__(494); /** * Generate tasks based on parent directory of each pattern. */ @@ -39333,16 +46229,16 @@ exports.convertPatternGroupToTask = convertPatternGroupToTask; /***/ }), -/* 413 */ +/* 494 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var path = __webpack_require__(16); -var globParent = __webpack_require__(414); -var isGlob = __webpack_require__(418); -var micromatch = __webpack_require__(419); +var globParent = __webpack_require__(495); +var isGlob = __webpack_require__(498); +var micromatch = __webpack_require__(499); var GLOBSTAR = '**'; /** * Return true for static pattern. @@ -39488,15 +46384,15 @@ exports.matchAny = matchAny; /***/ }), -/* 414 */ +/* 495 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var path = __webpack_require__(16); -var isglob = __webpack_require__(415); -var pathDirname = __webpack_require__(417); +var isglob = __webpack_require__(496); +var pathDirname = __webpack_require__(497); var isWin32 = __webpack_require__(11).platform() === 'win32'; module.exports = function globParent(str) { @@ -39519,7 +46415,7 @@ module.exports = function globParent(str) { /***/ }), -/* 415 */ +/* 496 */ /***/ (function(module, exports, __webpack_require__) { /*! @@ -39529,7 +46425,7 @@ module.exports = function globParent(str) { * Licensed under the MIT License. */ -var isExtglob = __webpack_require__(416); +var isExtglob = __webpack_require__(188); module.exports = function isGlob(str) { if (typeof str !== 'string' || str === '') { @@ -39550,33 +46446,7 @@ module.exports = function isGlob(str) { /***/ }), -/* 416 */ -/***/ (function(module, exports) { - -/*! - * is-extglob - * - * Copyright (c) 2014-2016, Jon Schlinkert. - * Licensed under the MIT License. - */ - -module.exports = function isExtglob(str) { - if (typeof str !== 'string' || str === '') { - return false; - } - - var match; - while ((match = /(\\).|([@?!+*]\(.*\))/g.exec(str))) { - if (match[2]) return true; - str = str.slice(match.index + match[0].length); - } - - return false; -}; - - -/***/ }), -/* 417 */ +/* 497 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -39726,7 +46596,7 @@ module.exports.win32 = win32; /***/ }), -/* 418 */ +/* 498 */ /***/ (function(module, exports, __webpack_require__) { /*! @@ -39736,7 +46606,7 @@ module.exports.win32 = win32; * Released under the MIT License. */ -var isExtglob = __webpack_require__(416); +var isExtglob = __webpack_require__(188); var chars = { '{': '}', '(': ')', '[': ']'}; module.exports = function isGlob(str, options) { @@ -39778,7 +46648,7 @@ module.exports = function isGlob(str, options) { /***/ }), -/* 419 */ +/* 499 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -39789,18 +46659,18 @@ module.exports = function isGlob(str, options) { */ var util = __webpack_require__(29); -var braces = __webpack_require__(420); -var toRegex = __webpack_require__(523); -var extend = __webpack_require__(531); +var braces = __webpack_require__(500); +var toRegex = __webpack_require__(603); +var extend = __webpack_require__(611); /** * Local dependencies */ -var compilers = __webpack_require__(534); -var parsers = __webpack_require__(561); -var cache = __webpack_require__(562); -var utils = __webpack_require__(563); +var compilers = __webpack_require__(614); +var parsers = __webpack_require__(641); +var cache = __webpack_require__(642); +var utils = __webpack_require__(643); var MAX_LENGTH = 1024 * 64; /** @@ -40662,7 +47532,7 @@ module.exports = micromatch; /***/ }), -/* 420 */ +/* 500 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -40672,18 +47542,18 @@ module.exports = micromatch; * Module dependencies */ -var toRegex = __webpack_require__(421); -var unique = __webpack_require__(433); -var extend = __webpack_require__(430); +var toRegex = __webpack_require__(501); +var unique = __webpack_require__(513); +var extend = __webpack_require__(510); /** * Local dependencies */ -var compilers = __webpack_require__(434); -var parsers = __webpack_require__(449); -var Braces = __webpack_require__(459); -var utils = __webpack_require__(435); +var compilers = __webpack_require__(514); +var parsers = __webpack_require__(529); +var Braces = __webpack_require__(539); +var utils = __webpack_require__(515); var MAX_LENGTH = 1024 * 64; var cache = {}; @@ -40987,15 +47857,15 @@ module.exports = braces; /***/ }), -/* 421 */ +/* 501 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var define = __webpack_require__(422); -var extend = __webpack_require__(430); -var not = __webpack_require__(432); +var define = __webpack_require__(502); +var extend = __webpack_require__(510); +var not = __webpack_require__(512); var MAX_LENGTH = 1024 * 64; /** @@ -41142,7 +48012,7 @@ module.exports.makeRe = makeRe; /***/ }), -/* 422 */ +/* 502 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -41155,7 +48025,7 @@ module.exports.makeRe = makeRe; -var isDescriptor = __webpack_require__(423); +var isDescriptor = __webpack_require__(503); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -41180,7 +48050,7 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 423 */ +/* 503 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -41193,9 +48063,9 @@ module.exports = function defineProperty(obj, prop, val) { -var typeOf = __webpack_require__(424); -var isAccessor = __webpack_require__(425); -var isData = __webpack_require__(428); +var typeOf = __webpack_require__(504); +var isAccessor = __webpack_require__(505); +var isData = __webpack_require__(508); module.exports = function isDescriptor(obj, key) { if (typeOf(obj) !== 'object') { @@ -41209,7 +48079,7 @@ module.exports = function isDescriptor(obj, key) { /***/ }), -/* 424 */ +/* 504 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -41362,7 +48232,7 @@ function isBuffer(val) { /***/ }), -/* 425 */ +/* 505 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -41375,7 +48245,7 @@ function isBuffer(val) { -var typeOf = __webpack_require__(426); +var typeOf = __webpack_require__(506); // accessor descriptor properties var accessor = { @@ -41438,10 +48308,10 @@ module.exports = isAccessorDescriptor; /***/ }), -/* 426 */ +/* 506 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(427); +var isBuffer = __webpack_require__(507); var toString = Object.prototype.toString; /** @@ -41560,7 +48430,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 427 */ +/* 507 */ /***/ (function(module, exports) { /*! @@ -41587,7 +48457,7 @@ function isSlowBuffer (obj) { /***/ }), -/* 428 */ +/* 508 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -41600,7 +48470,7 @@ function isSlowBuffer (obj) { -var typeOf = __webpack_require__(429); +var typeOf = __webpack_require__(509); // data descriptor properties var data = { @@ -41649,10 +48519,10 @@ module.exports = isDataDescriptor; /***/ }), -/* 429 */ +/* 509 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(427); +var isBuffer = __webpack_require__(507); var toString = Object.prototype.toString; /** @@ -41771,13 +48641,13 @@ module.exports = function kindOf(val) { /***/ }), -/* 430 */ +/* 510 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(431); +var isObject = __webpack_require__(511); module.exports = function extend(o/*, objects*/) { if (!isObject(o)) { o = {}; } @@ -41811,7 +48681,7 @@ function hasOwn(obj, key) { /***/ }), -/* 431 */ +/* 511 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -41831,13 +48701,13 @@ module.exports = function isExtendable(val) { /***/ }), -/* 432 */ +/* 512 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extend = __webpack_require__(430); +var extend = __webpack_require__(510); /** * The main export is a function that takes a `pattern` string and an `options` object. @@ -41904,7 +48774,7 @@ module.exports = toRegex; /***/ }), -/* 433 */ +/* 513 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -41954,13 +48824,13 @@ module.exports.immutable = function uniqueImmutable(arr) { /***/ }), -/* 434 */ +/* 514 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(435); +var utils = __webpack_require__(515); module.exports = function(braces, options) { braces.compiler @@ -42243,25 +49113,25 @@ function hasQueue(node) { /***/ }), -/* 435 */ +/* 515 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var splitString = __webpack_require__(436); +var splitString = __webpack_require__(516); var utils = module.exports; /** * Module dependencies */ -utils.extend = __webpack_require__(430); -utils.flatten = __webpack_require__(442); -utils.isObject = __webpack_require__(440); -utils.fillRange = __webpack_require__(443); -utils.repeat = __webpack_require__(448); -utils.unique = __webpack_require__(433); +utils.extend = __webpack_require__(510); +utils.flatten = __webpack_require__(522); +utils.isObject = __webpack_require__(520); +utils.fillRange = __webpack_require__(523); +utils.repeat = __webpack_require__(528); +utils.unique = __webpack_require__(513); utils.define = function(obj, key, val) { Object.defineProperty(obj, key, { @@ -42593,7 +49463,7 @@ utils.escapeRegex = function(str) { /***/ }), -/* 436 */ +/* 516 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -42606,7 +49476,7 @@ utils.escapeRegex = function(str) { -var extend = __webpack_require__(437); +var extend = __webpack_require__(517); module.exports = function(str, options, fn) { if (typeof str !== 'string') { @@ -42771,14 +49641,14 @@ function keepEscaping(opts, str, idx) { /***/ }), -/* 437 */ +/* 517 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(438); -var assignSymbols = __webpack_require__(441); +var isExtendable = __webpack_require__(518); +var assignSymbols = __webpack_require__(521); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -42838,7 +49708,7 @@ function isEnum(obj, key) { /***/ }), -/* 438 */ +/* 518 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -42851,7 +49721,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(439); +var isPlainObject = __webpack_require__(519); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -42859,7 +49729,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 439 */ +/* 519 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -42872,7 +49742,7 @@ module.exports = function isExtendable(val) { -var isObject = __webpack_require__(440); +var isObject = __webpack_require__(520); function isObjectObject(o) { return isObject(o) === true @@ -42903,7 +49773,7 @@ module.exports = function isPlainObject(o) { /***/ }), -/* 440 */ +/* 520 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -42922,7 +49792,7 @@ module.exports = function isObject(val) { /***/ }), -/* 441 */ +/* 521 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -42969,7 +49839,7 @@ module.exports = function(receiver, objects) { /***/ }), -/* 442 */ +/* 522 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -42998,7 +49868,7 @@ function flat(arr, res) { /***/ }), -/* 443 */ +/* 523 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -43012,10 +49882,10 @@ function flat(arr, res) { var util = __webpack_require__(29); -var isNumber = __webpack_require__(444); -var extend = __webpack_require__(430); -var repeat = __webpack_require__(446); -var toRegex = __webpack_require__(447); +var isNumber = __webpack_require__(524); +var extend = __webpack_require__(510); +var repeat = __webpack_require__(526); +var toRegex = __webpack_require__(527); /** * Return a range of numbers or letters. @@ -43213,7 +50083,7 @@ module.exports = fillRange; /***/ }), -/* 444 */ +/* 524 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -43226,7 +50096,7 @@ module.exports = fillRange; -var typeOf = __webpack_require__(445); +var typeOf = __webpack_require__(525); module.exports = function isNumber(num) { var type = typeOf(num); @@ -43242,10 +50112,10 @@ module.exports = function isNumber(num) { /***/ }), -/* 445 */ +/* 525 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(427); +var isBuffer = __webpack_require__(507); var toString = Object.prototype.toString; /** @@ -43364,7 +50234,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 446 */ +/* 526 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -43441,7 +50311,7 @@ function repeat(str, num) { /***/ }), -/* 447 */ +/* 527 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -43454,8 +50324,8 @@ function repeat(str, num) { -var repeat = __webpack_require__(446); -var isNumber = __webpack_require__(444); +var repeat = __webpack_require__(526); +var isNumber = __webpack_require__(524); var cache = {}; function toRegexRange(min, max, options) { @@ -43742,7 +50612,7 @@ module.exports = toRegexRange; /***/ }), -/* 448 */ +/* 528 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -43767,14 +50637,14 @@ module.exports = function repeat(ele, num) { /***/ }), -/* 449 */ +/* 529 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var Node = __webpack_require__(450); -var utils = __webpack_require__(435); +var Node = __webpack_require__(530); +var utils = __webpack_require__(515); /** * Braces parsers @@ -44134,15 +51004,15 @@ function concatNodes(pos, node, parent, options) { /***/ }), -/* 450 */ +/* 530 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(440); -var define = __webpack_require__(451); -var utils = __webpack_require__(458); +var isObject = __webpack_require__(520); +var define = __webpack_require__(531); +var utils = __webpack_require__(538); var ownNames; /** @@ -44633,7 +51503,7 @@ exports = module.exports = Node; /***/ }), -/* 451 */ +/* 531 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -44646,7 +51516,7 @@ exports = module.exports = Node; -var isDescriptor = __webpack_require__(452); +var isDescriptor = __webpack_require__(532); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -44671,7 +51541,7 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 452 */ +/* 532 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -44684,9 +51554,9 @@ module.exports = function defineProperty(obj, prop, val) { -var typeOf = __webpack_require__(453); -var isAccessor = __webpack_require__(454); -var isData = __webpack_require__(456); +var typeOf = __webpack_require__(533); +var isAccessor = __webpack_require__(534); +var isData = __webpack_require__(536); module.exports = function isDescriptor(obj, key) { if (typeOf(obj) !== 'object') { @@ -44700,7 +51570,7 @@ module.exports = function isDescriptor(obj, key) { /***/ }), -/* 453 */ +/* 533 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -44835,7 +51705,7 @@ function isBuffer(val) { /***/ }), -/* 454 */ +/* 534 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -44848,7 +51718,7 @@ function isBuffer(val) { -var typeOf = __webpack_require__(455); +var typeOf = __webpack_require__(535); // accessor descriptor properties var accessor = { @@ -44911,7 +51781,7 @@ module.exports = isAccessorDescriptor; /***/ }), -/* 455 */ +/* 535 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -45046,7 +51916,7 @@ function isBuffer(val) { /***/ }), -/* 456 */ +/* 536 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -45059,7 +51929,7 @@ function isBuffer(val) { -var typeOf = __webpack_require__(457); +var typeOf = __webpack_require__(537); module.exports = function isDataDescriptor(obj, prop) { // data descriptor properties @@ -45102,7 +51972,7 @@ module.exports = function isDataDescriptor(obj, prop) { /***/ }), -/* 457 */ +/* 537 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -45237,13 +52107,13 @@ function isBuffer(val) { /***/ }), -/* 458 */ +/* 538 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var typeOf = __webpack_require__(445); +var typeOf = __webpack_require__(525); var utils = module.exports; /** @@ -46263,17 +53133,17 @@ function assert(val, message) { /***/ }), -/* 459 */ +/* 539 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extend = __webpack_require__(430); -var Snapdragon = __webpack_require__(460); -var compilers = __webpack_require__(434); -var parsers = __webpack_require__(449); -var utils = __webpack_require__(435); +var extend = __webpack_require__(510); +var Snapdragon = __webpack_require__(540); +var compilers = __webpack_require__(514); +var parsers = __webpack_require__(529); +var utils = __webpack_require__(515); /** * Customize Snapdragon parser and renderer @@ -46374,17 +53244,17 @@ module.exports = Braces; /***/ }), -/* 460 */ +/* 540 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var Base = __webpack_require__(461); -var define = __webpack_require__(422); -var Compiler = __webpack_require__(490); -var Parser = __webpack_require__(520); -var utils = __webpack_require__(500); +var Base = __webpack_require__(541); +var define = __webpack_require__(502); +var Compiler = __webpack_require__(570); +var Parser = __webpack_require__(600); +var utils = __webpack_require__(580); var regexCache = {}; var cache = {}; @@ -46555,20 +53425,20 @@ module.exports.Parser = Parser; /***/ }), -/* 461 */ +/* 541 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(29); -var define = __webpack_require__(462); -var CacheBase = __webpack_require__(463); -var Emitter = __webpack_require__(464); -var isObject = __webpack_require__(440); -var merge = __webpack_require__(481); -var pascal = __webpack_require__(484); -var cu = __webpack_require__(485); +var define = __webpack_require__(542); +var CacheBase = __webpack_require__(543); +var Emitter = __webpack_require__(544); +var isObject = __webpack_require__(520); +var merge = __webpack_require__(561); +var pascal = __webpack_require__(564); +var cu = __webpack_require__(565); /** * Optionally define a custom `cache` namespace to use. @@ -46997,7 +53867,7 @@ module.exports.namespace = namespace; /***/ }), -/* 462 */ +/* 542 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -47010,7 +53880,7 @@ module.exports.namespace = namespace; -var isDescriptor = __webpack_require__(452); +var isDescriptor = __webpack_require__(532); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -47035,21 +53905,21 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 463 */ +/* 543 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(440); -var Emitter = __webpack_require__(464); -var visit = __webpack_require__(465); -var toPath = __webpack_require__(468); -var union = __webpack_require__(469); -var del = __webpack_require__(473); -var get = __webpack_require__(471); -var has = __webpack_require__(478); -var set = __webpack_require__(472); +var isObject = __webpack_require__(520); +var Emitter = __webpack_require__(544); +var visit = __webpack_require__(545); +var toPath = __webpack_require__(548); +var union = __webpack_require__(549); +var del = __webpack_require__(553); +var get = __webpack_require__(551); +var has = __webpack_require__(558); +var set = __webpack_require__(552); /** * Create a `Cache` constructor that when instantiated will @@ -47303,7 +54173,7 @@ module.exports.namespace = namespace; /***/ }), -/* 464 */ +/* 544 */ /***/ (function(module, exports, __webpack_require__) { @@ -47472,7 +54342,7 @@ Emitter.prototype.hasListeners = function(event){ /***/ }), -/* 465 */ +/* 545 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -47485,8 +54355,8 @@ Emitter.prototype.hasListeners = function(event){ -var visit = __webpack_require__(466); -var mapVisit = __webpack_require__(467); +var visit = __webpack_require__(546); +var mapVisit = __webpack_require__(547); module.exports = function(collection, method, val) { var result; @@ -47509,7 +54379,7 @@ module.exports = function(collection, method, val) { /***/ }), -/* 466 */ +/* 546 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -47522,7 +54392,7 @@ module.exports = function(collection, method, val) { -var isObject = __webpack_require__(440); +var isObject = __webpack_require__(520); module.exports = function visit(thisArg, method, target, val) { if (!isObject(thisArg) && typeof thisArg !== 'function') { @@ -47549,14 +54419,14 @@ module.exports = function visit(thisArg, method, target, val) { /***/ }), -/* 467 */ +/* 547 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(29); -var visit = __webpack_require__(466); +var visit = __webpack_require__(546); /** * Map `visit` over an array of objects. @@ -47593,7 +54463,7 @@ function isObject(val) { /***/ }), -/* 468 */ +/* 548 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -47606,7 +54476,7 @@ function isObject(val) { -var typeOf = __webpack_require__(445); +var typeOf = __webpack_require__(525); module.exports = function toPath(args) { if (typeOf(args) !== 'arguments') { @@ -47633,16 +54503,16 @@ function filter(arr) { /***/ }), -/* 469 */ +/* 549 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(431); -var union = __webpack_require__(470); -var get = __webpack_require__(471); -var set = __webpack_require__(472); +var isObject = __webpack_require__(511); +var union = __webpack_require__(550); +var get = __webpack_require__(551); +var set = __webpack_require__(552); module.exports = function unionValue(obj, prop, value) { if (!isObject(obj)) { @@ -47670,7 +54540,7 @@ function arrayify(val) { /***/ }), -/* 470 */ +/* 550 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -47706,7 +54576,7 @@ module.exports = function union(init) { /***/ }), -/* 471 */ +/* 551 */ /***/ (function(module, exports) { /*! @@ -47762,7 +54632,7 @@ function toString(val) { /***/ }), -/* 472 */ +/* 552 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -47775,10 +54645,10 @@ function toString(val) { -var split = __webpack_require__(436); -var extend = __webpack_require__(430); -var isPlainObject = __webpack_require__(439); -var isObject = __webpack_require__(431); +var split = __webpack_require__(516); +var extend = __webpack_require__(510); +var isPlainObject = __webpack_require__(519); +var isObject = __webpack_require__(511); module.exports = function(obj, prop, val) { if (!isObject(obj)) { @@ -47824,7 +54694,7 @@ function isValidKey(key) { /***/ }), -/* 473 */ +/* 553 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -47837,8 +54707,8 @@ function isValidKey(key) { -var isObject = __webpack_require__(440); -var has = __webpack_require__(474); +var isObject = __webpack_require__(520); +var has = __webpack_require__(554); module.exports = function unset(obj, prop) { if (!isObject(obj)) { @@ -47863,7 +54733,7 @@ module.exports = function unset(obj, prop) { /***/ }), -/* 474 */ +/* 554 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -47876,9 +54746,9 @@ module.exports = function unset(obj, prop) { -var isObject = __webpack_require__(475); -var hasValues = __webpack_require__(477); -var get = __webpack_require__(471); +var isObject = __webpack_require__(555); +var hasValues = __webpack_require__(557); +var get = __webpack_require__(551); module.exports = function(obj, prop, noZero) { if (isObject(obj)) { @@ -47889,7 +54759,7 @@ module.exports = function(obj, prop, noZero) { /***/ }), -/* 475 */ +/* 555 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -47902,7 +54772,7 @@ module.exports = function(obj, prop, noZero) { -var isArray = __webpack_require__(476); +var isArray = __webpack_require__(556); module.exports = function isObject(val) { return val != null && typeof val === 'object' && isArray(val) === false; @@ -47910,7 +54780,7 @@ module.exports = function isObject(val) { /***/ }), -/* 476 */ +/* 556 */ /***/ (function(module, exports) { var toString = {}.toString; @@ -47921,7 +54791,7 @@ module.exports = Array.isArray || function (arr) { /***/ }), -/* 477 */ +/* 557 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -47964,7 +54834,7 @@ module.exports = function hasValue(o, noZero) { /***/ }), -/* 478 */ +/* 558 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -47977,9 +54847,9 @@ module.exports = function hasValue(o, noZero) { -var isObject = __webpack_require__(440); -var hasValues = __webpack_require__(479); -var get = __webpack_require__(471); +var isObject = __webpack_require__(520); +var hasValues = __webpack_require__(559); +var get = __webpack_require__(551); module.exports = function(val, prop) { return hasValues(isObject(val) && prop ? get(val, prop) : val); @@ -47987,7 +54857,7 @@ module.exports = function(val, prop) { /***/ }), -/* 479 */ +/* 559 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -48000,8 +54870,8 @@ module.exports = function(val, prop) { -var typeOf = __webpack_require__(480); -var isNumber = __webpack_require__(444); +var typeOf = __webpack_require__(560); +var isNumber = __webpack_require__(524); module.exports = function hasValue(val) { // is-number checks for NaN and other edge cases @@ -48054,10 +54924,10 @@ module.exports = function hasValue(val) { /***/ }), -/* 480 */ +/* 560 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(427); +var isBuffer = __webpack_require__(507); var toString = Object.prototype.toString; /** @@ -48179,14 +55049,14 @@ module.exports = function kindOf(val) { /***/ }), -/* 481 */ +/* 561 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(482); -var forIn = __webpack_require__(483); +var isExtendable = __webpack_require__(562); +var forIn = __webpack_require__(563); function mixinDeep(target, objects) { var len = arguments.length, i = 0; @@ -48250,7 +55120,7 @@ module.exports = mixinDeep; /***/ }), -/* 482 */ +/* 562 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -48263,7 +55133,7 @@ module.exports = mixinDeep; -var isPlainObject = __webpack_require__(439); +var isPlainObject = __webpack_require__(519); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -48271,7 +55141,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 483 */ +/* 563 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -48294,7 +55164,7 @@ module.exports = function forIn(obj, fn, thisArg) { /***/ }), -/* 484 */ +/* 564 */ /***/ (function(module, exports) { /*! @@ -48321,14 +55191,14 @@ module.exports = pascalcase; /***/ }), -/* 485 */ +/* 565 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(29); -var utils = __webpack_require__(486); +var utils = __webpack_require__(566); /** * Expose class utils @@ -48693,7 +55563,7 @@ cu.bubble = function(Parent, events) { /***/ }), -/* 486 */ +/* 566 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -48707,10 +55577,10 @@ var utils = {}; * Lazily required module dependencies */ -utils.union = __webpack_require__(470); -utils.define = __webpack_require__(422); -utils.isObj = __webpack_require__(440); -utils.staticExtend = __webpack_require__(487); +utils.union = __webpack_require__(550); +utils.define = __webpack_require__(502); +utils.isObj = __webpack_require__(520); +utils.staticExtend = __webpack_require__(567); /** @@ -48721,7 +55591,7 @@ module.exports = utils; /***/ }), -/* 487 */ +/* 567 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -48734,8 +55604,8 @@ module.exports = utils; -var copy = __webpack_require__(488); -var define = __webpack_require__(422); +var copy = __webpack_require__(568); +var define = __webpack_require__(502); var util = __webpack_require__(29); /** @@ -48818,15 +55688,15 @@ module.exports = extend; /***/ }), -/* 488 */ +/* 568 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var typeOf = __webpack_require__(445); -var copyDescriptor = __webpack_require__(489); -var define = __webpack_require__(422); +var typeOf = __webpack_require__(525); +var copyDescriptor = __webpack_require__(569); +var define = __webpack_require__(502); /** * Copy static properties, prototype properties, and descriptors from one object to another. @@ -48999,7 +55869,7 @@ module.exports.has = has; /***/ }), -/* 489 */ +/* 569 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -49087,16 +55957,16 @@ function isObject(val) { /***/ }), -/* 490 */ +/* 570 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var use = __webpack_require__(491); -var define = __webpack_require__(422); -var debug = __webpack_require__(493)('snapdragon:compiler'); -var utils = __webpack_require__(500); +var use = __webpack_require__(571); +var define = __webpack_require__(502); +var debug = __webpack_require__(573)('snapdragon:compiler'); +var utils = __webpack_require__(580); /** * Create a new `Compiler` with the given `options`. @@ -49250,7 +56120,7 @@ Compiler.prototype = { // source map support if (opts.sourcemap) { - var sourcemaps = __webpack_require__(519); + var sourcemaps = __webpack_require__(599); sourcemaps(this); this.mapVisit(this.ast.nodes); this.applySourceMaps(); @@ -49271,7 +56141,7 @@ module.exports = Compiler; /***/ }), -/* 491 */ +/* 571 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -49284,7 +56154,7 @@ module.exports = Compiler; -var utils = __webpack_require__(492); +var utils = __webpack_require__(572); module.exports = function base(app, opts) { if (!utils.isObject(app) && typeof app !== 'function') { @@ -49399,7 +56269,7 @@ module.exports = function base(app, opts) { /***/ }), -/* 492 */ +/* 572 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -49413,8 +56283,8 @@ var utils = {}; * Lazily required module dependencies */ -utils.define = __webpack_require__(422); -utils.isObject = __webpack_require__(440); +utils.define = __webpack_require__(502); +utils.isObject = __webpack_require__(520); utils.isString = function(val) { @@ -49429,7 +56299,7 @@ module.exports = utils; /***/ }), -/* 493 */ +/* 573 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -49438,14 +56308,14 @@ module.exports = utils; */ if (typeof process !== 'undefined' && process.type === 'renderer') { - module.exports = __webpack_require__(494); + module.exports = __webpack_require__(574); } else { - module.exports = __webpack_require__(497); + module.exports = __webpack_require__(577); } /***/ }), -/* 494 */ +/* 574 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -49454,7 +56324,7 @@ if (typeof process !== 'undefined' && process.type === 'renderer') { * Expose `debug()` as the module. */ -exports = module.exports = __webpack_require__(495); +exports = module.exports = __webpack_require__(575); exports.log = log; exports.formatArgs = formatArgs; exports.save = save; @@ -49636,7 +56506,7 @@ function localstorage() { /***/ }), -/* 495 */ +/* 575 */ /***/ (function(module, exports, __webpack_require__) { @@ -49652,7 +56522,7 @@ exports.coerce = coerce; exports.disable = disable; exports.enable = enable; exports.enabled = enabled; -exports.humanize = __webpack_require__(496); +exports.humanize = __webpack_require__(576); /** * The currently active debug mode names, and names to skip. @@ -49844,7 +56714,7 @@ function coerce(val) { /***/ }), -/* 496 */ +/* 576 */ /***/ (function(module, exports) { /** @@ -50002,14 +56872,14 @@ function plural(ms, n, name) { /***/ }), -/* 497 */ +/* 577 */ /***/ (function(module, exports, __webpack_require__) { /** * Module dependencies. */ -var tty = __webpack_require__(498); +var tty = __webpack_require__(578); var util = __webpack_require__(29); /** @@ -50018,7 +56888,7 @@ var util = __webpack_require__(29); * Expose `debug()` as the module. */ -exports = module.exports = __webpack_require__(495); +exports = module.exports = __webpack_require__(575); exports.init = init; exports.log = log; exports.formatArgs = formatArgs; @@ -50197,7 +57067,7 @@ function createWritableStdioStream (fd) { case 'PIPE': case 'TCP': - var net = __webpack_require__(499); + var net = __webpack_require__(579); stream = new net.Socket({ fd: fd, readable: false, @@ -50256,19 +57126,19 @@ exports.enable(load()); /***/ }), -/* 498 */ +/* 578 */ /***/ (function(module, exports) { module.exports = require("tty"); /***/ }), -/* 499 */ +/* 579 */ /***/ (function(module, exports) { module.exports = require("net"); /***/ }), -/* 500 */ +/* 580 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -50278,9 +57148,9 @@ module.exports = require("net"); * Module dependencies */ -exports.extend = __webpack_require__(430); -exports.SourceMap = __webpack_require__(501); -exports.sourceMapResolve = __webpack_require__(512); +exports.extend = __webpack_require__(510); +exports.SourceMap = __webpack_require__(581); +exports.sourceMapResolve = __webpack_require__(592); /** * Convert backslash in the given string to forward slashes @@ -50323,7 +57193,7 @@ exports.last = function(arr, n) { /***/ }), -/* 501 */ +/* 581 */ /***/ (function(module, exports, __webpack_require__) { /* @@ -50331,13 +57201,13 @@ exports.last = function(arr, n) { * Licensed under the New BSD license. See LICENSE.txt or: * http://opensource.org/licenses/BSD-3-Clause */ -exports.SourceMapGenerator = __webpack_require__(502).SourceMapGenerator; -exports.SourceMapConsumer = __webpack_require__(508).SourceMapConsumer; -exports.SourceNode = __webpack_require__(511).SourceNode; +exports.SourceMapGenerator = __webpack_require__(582).SourceMapGenerator; +exports.SourceMapConsumer = __webpack_require__(588).SourceMapConsumer; +exports.SourceNode = __webpack_require__(591).SourceNode; /***/ }), -/* 502 */ +/* 582 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -50347,10 +57217,10 @@ exports.SourceNode = __webpack_require__(511).SourceNode; * http://opensource.org/licenses/BSD-3-Clause */ -var base64VLQ = __webpack_require__(503); -var util = __webpack_require__(505); -var ArraySet = __webpack_require__(506).ArraySet; -var MappingList = __webpack_require__(507).MappingList; +var base64VLQ = __webpack_require__(583); +var util = __webpack_require__(585); +var ArraySet = __webpack_require__(586).ArraySet; +var MappingList = __webpack_require__(587).MappingList; /** * An instance of the SourceMapGenerator represents a source map which is @@ -50759,7 +57629,7 @@ exports.SourceMapGenerator = SourceMapGenerator; /***/ }), -/* 503 */ +/* 583 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -50799,7 +57669,7 @@ exports.SourceMapGenerator = SourceMapGenerator; * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -var base64 = __webpack_require__(504); +var base64 = __webpack_require__(584); // A single base 64 digit can contain 6 bits of data. For the base 64 variable // length quantities we use in the source map spec, the first bit is the sign, @@ -50905,7 +57775,7 @@ exports.decode = function base64VLQ_decode(aStr, aIndex, aOutParam) { /***/ }), -/* 504 */ +/* 584 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -50978,7 +57848,7 @@ exports.decode = function (charCode) { /***/ }), -/* 505 */ +/* 585 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -51401,7 +58271,7 @@ exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflate /***/ }), -/* 506 */ +/* 586 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -51411,7 +58281,7 @@ exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflate * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(505); +var util = __webpack_require__(585); var has = Object.prototype.hasOwnProperty; var hasNativeMap = typeof Map !== "undefined"; @@ -51528,7 +58398,7 @@ exports.ArraySet = ArraySet; /***/ }), -/* 507 */ +/* 587 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -51538,7 +58408,7 @@ exports.ArraySet = ArraySet; * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(505); +var util = __webpack_require__(585); /** * Determine whether mappingB is after mappingA with respect to generated @@ -51613,7 +58483,7 @@ exports.MappingList = MappingList; /***/ }), -/* 508 */ +/* 588 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -51623,11 +58493,11 @@ exports.MappingList = MappingList; * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(505); -var binarySearch = __webpack_require__(509); -var ArraySet = __webpack_require__(506).ArraySet; -var base64VLQ = __webpack_require__(503); -var quickSort = __webpack_require__(510).quickSort; +var util = __webpack_require__(585); +var binarySearch = __webpack_require__(589); +var ArraySet = __webpack_require__(586).ArraySet; +var base64VLQ = __webpack_require__(583); +var quickSort = __webpack_require__(590).quickSort; function SourceMapConsumer(aSourceMap) { var sourceMap = aSourceMap; @@ -52701,7 +59571,7 @@ exports.IndexedSourceMapConsumer = IndexedSourceMapConsumer; /***/ }), -/* 509 */ +/* 589 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -52818,7 +59688,7 @@ exports.search = function search(aNeedle, aHaystack, aCompare, aBias) { /***/ }), -/* 510 */ +/* 590 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -52938,7 +59808,7 @@ exports.quickSort = function (ary, comparator) { /***/ }), -/* 511 */ +/* 591 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -52948,8 +59818,8 @@ exports.quickSort = function (ary, comparator) { * http://opensource.org/licenses/BSD-3-Clause */ -var SourceMapGenerator = __webpack_require__(502).SourceMapGenerator; -var util = __webpack_require__(505); +var SourceMapGenerator = __webpack_require__(582).SourceMapGenerator; +var util = __webpack_require__(585); // Matches a Windows-style `\r\n` newline or a `\n` newline used by all other // operating systems these days (capturing the result). @@ -53357,17 +60227,17 @@ exports.SourceNode = SourceNode; /***/ }), -/* 512 */ +/* 592 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014, 2015, 2016, 2017 Simon Lydell // X11 (“MIT”) Licensed. (See LICENSE.) -var sourceMappingURL = __webpack_require__(513) -var resolveUrl = __webpack_require__(514) -var decodeUriComponent = __webpack_require__(515) -var urix = __webpack_require__(517) -var atob = __webpack_require__(518) +var sourceMappingURL = __webpack_require__(593) +var resolveUrl = __webpack_require__(594) +var decodeUriComponent = __webpack_require__(595) +var urix = __webpack_require__(597) +var atob = __webpack_require__(598) @@ -53665,7 +60535,7 @@ module.exports = { /***/ }), -/* 513 */ +/* 593 */ /***/ (function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;// Copyright 2014 Simon Lydell @@ -53728,7 +60598,7 @@ void (function(root, factory) { /***/ }), -/* 514 */ +/* 594 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014 Simon Lydell @@ -53746,13 +60616,13 @@ module.exports = resolveUrl /***/ }), -/* 515 */ +/* 595 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2017 Simon Lydell // X11 (“MIT”) Licensed. (See LICENSE.) -var decodeUriComponent = __webpack_require__(516) +var decodeUriComponent = __webpack_require__(596) function customDecodeUriComponent(string) { // `decodeUriComponent` turns `+` into ` `, but that's not wanted. @@ -53763,7 +60633,7 @@ module.exports = customDecodeUriComponent /***/ }), -/* 516 */ +/* 596 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -53864,7 +60734,7 @@ module.exports = function (encodedURI) { /***/ }), -/* 517 */ +/* 597 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014 Simon Lydell @@ -53887,7 +60757,7 @@ module.exports = urix /***/ }), -/* 518 */ +/* 598 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -53901,7 +60771,7 @@ module.exports = atob.atob = atob; /***/ }), -/* 519 */ +/* 599 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -53909,8 +60779,8 @@ module.exports = atob.atob = atob; var fs = __webpack_require__(23); var path = __webpack_require__(16); -var define = __webpack_require__(422); -var utils = __webpack_require__(500); +var define = __webpack_require__(502); +var utils = __webpack_require__(580); /** * Expose `mixin()`. @@ -54053,19 +60923,19 @@ exports.comment = function(node) { /***/ }), -/* 520 */ +/* 600 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var use = __webpack_require__(491); +var use = __webpack_require__(571); var util = __webpack_require__(29); -var Cache = __webpack_require__(521); -var define = __webpack_require__(422); -var debug = __webpack_require__(493)('snapdragon:parser'); -var Position = __webpack_require__(522); -var utils = __webpack_require__(500); +var Cache = __webpack_require__(601); +var define = __webpack_require__(502); +var debug = __webpack_require__(573)('snapdragon:parser'); +var Position = __webpack_require__(602); +var utils = __webpack_require__(580); /** * Create a new `Parser` with the given `input` and `options`. @@ -54593,7 +61463,7 @@ module.exports = Parser; /***/ }), -/* 521 */ +/* 601 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -54700,13 +61570,13 @@ MapCache.prototype.del = function mapDelete(key) { /***/ }), -/* 522 */ +/* 602 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var define = __webpack_require__(422); +var define = __webpack_require__(502); /** * Store position for a node @@ -54721,16 +61591,16 @@ module.exports = function Position(start, parser) { /***/ }), -/* 523 */ +/* 603 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var safe = __webpack_require__(524); -var define = __webpack_require__(530); -var extend = __webpack_require__(531); -var not = __webpack_require__(533); +var safe = __webpack_require__(604); +var define = __webpack_require__(610); +var extend = __webpack_require__(611); +var not = __webpack_require__(613); var MAX_LENGTH = 1024 * 64; /** @@ -54883,10 +61753,10 @@ module.exports.makeRe = makeRe; /***/ }), -/* 524 */ +/* 604 */ /***/ (function(module, exports, __webpack_require__) { -var parse = __webpack_require__(525); +var parse = __webpack_require__(605); var types = parse.types; module.exports = function (re, opts) { @@ -54932,13 +61802,13 @@ function isRegExp (x) { /***/ }), -/* 525 */ +/* 605 */ /***/ (function(module, exports, __webpack_require__) { -var util = __webpack_require__(526); -var types = __webpack_require__(527); -var sets = __webpack_require__(528); -var positions = __webpack_require__(529); +var util = __webpack_require__(606); +var types = __webpack_require__(607); +var sets = __webpack_require__(608); +var positions = __webpack_require__(609); module.exports = function(regexpStr) { @@ -55220,11 +62090,11 @@ module.exports.types = types; /***/ }), -/* 526 */ +/* 606 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(527); -var sets = __webpack_require__(528); +var types = __webpack_require__(607); +var sets = __webpack_require__(608); // All of these are private and only used by randexp. @@ -55337,7 +62207,7 @@ exports.error = function(regexp, msg) { /***/ }), -/* 527 */ +/* 607 */ /***/ (function(module, exports) { module.exports = { @@ -55353,10 +62223,10 @@ module.exports = { /***/ }), -/* 528 */ +/* 608 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(527); +var types = __webpack_require__(607); var INTS = function() { return [{ type: types.RANGE , from: 48, to: 57 }]; @@ -55441,10 +62311,10 @@ exports.anyChar = function() { /***/ }), -/* 529 */ +/* 609 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(527); +var types = __webpack_require__(607); exports.wordBoundary = function() { return { type: types.POSITION, value: 'b' }; @@ -55464,7 +62334,7 @@ exports.end = function() { /***/ }), -/* 530 */ +/* 610 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -55477,8 +62347,8 @@ exports.end = function() { -var isobject = __webpack_require__(440); -var isDescriptor = __webpack_require__(452); +var isobject = __webpack_require__(520); +var isDescriptor = __webpack_require__(532); var define = (typeof Reflect !== 'undefined' && Reflect.defineProperty) ? Reflect.defineProperty : Object.defineProperty; @@ -55509,14 +62379,14 @@ module.exports = function defineProperty(obj, key, val) { /***/ }), -/* 531 */ +/* 611 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(532); -var assignSymbols = __webpack_require__(441); +var isExtendable = __webpack_require__(612); +var assignSymbols = __webpack_require__(521); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -55576,7 +62446,7 @@ function isEnum(obj, key) { /***/ }), -/* 532 */ +/* 612 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -55589,7 +62459,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(439); +var isPlainObject = __webpack_require__(519); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -55597,14 +62467,14 @@ module.exports = function isExtendable(val) { /***/ }), -/* 533 */ +/* 613 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extend = __webpack_require__(531); -var safe = __webpack_require__(524); +var extend = __webpack_require__(611); +var safe = __webpack_require__(604); /** * The main export is a function that takes a `pattern` string and an `options` object. @@ -55676,14 +62546,14 @@ module.exports = toRegex; /***/ }), -/* 534 */ +/* 614 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var nanomatch = __webpack_require__(535); -var extglob = __webpack_require__(550); +var nanomatch = __webpack_require__(615); +var extglob = __webpack_require__(630); module.exports = function(snapdragon) { var compilers = snapdragon.compiler.compilers; @@ -55760,7 +62630,7 @@ function escapeExtglobs(compiler) { /***/ }), -/* 535 */ +/* 615 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -55771,17 +62641,17 @@ function escapeExtglobs(compiler) { */ var util = __webpack_require__(29); -var toRegex = __webpack_require__(421); -var extend = __webpack_require__(536); +var toRegex = __webpack_require__(501); +var extend = __webpack_require__(616); /** * Local dependencies */ -var compilers = __webpack_require__(538); -var parsers = __webpack_require__(539); -var cache = __webpack_require__(542); -var utils = __webpack_require__(544); +var compilers = __webpack_require__(618); +var parsers = __webpack_require__(619); +var cache = __webpack_require__(622); +var utils = __webpack_require__(624); var MAX_LENGTH = 1024 * 64; /** @@ -56605,14 +63475,14 @@ module.exports = nanomatch; /***/ }), -/* 536 */ +/* 616 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(537); -var assignSymbols = __webpack_require__(441); +var isExtendable = __webpack_require__(617); +var assignSymbols = __webpack_require__(521); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -56672,7 +63542,7 @@ function isEnum(obj, key) { /***/ }), -/* 537 */ +/* 617 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -56685,7 +63555,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(439); +var isPlainObject = __webpack_require__(519); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -56693,7 +63563,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 538 */ +/* 618 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -57039,15 +63909,15 @@ module.exports = function(nanomatch, options) { /***/ }), -/* 539 */ +/* 619 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var regexNot = __webpack_require__(432); -var toRegex = __webpack_require__(421); -var isOdd = __webpack_require__(540); +var regexNot = __webpack_require__(512); +var toRegex = __webpack_require__(501); +var isOdd = __webpack_require__(620); /** * Characters to use in negation regex (we want to "not" match @@ -57433,7 +64303,7 @@ module.exports.not = NOT_REGEX; /***/ }), -/* 540 */ +/* 620 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -57446,7 +64316,7 @@ module.exports.not = NOT_REGEX; -var isNumber = __webpack_require__(541); +var isNumber = __webpack_require__(621); module.exports = function isOdd(i) { if (!isNumber(i)) { @@ -57460,7 +64330,7 @@ module.exports = function isOdd(i) { /***/ }), -/* 541 */ +/* 621 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -57488,14 +64358,14 @@ module.exports = function isNumber(num) { /***/ }), -/* 542 */ +/* 622 */ /***/ (function(module, exports, __webpack_require__) { -module.exports = new (__webpack_require__(543))(); +module.exports = new (__webpack_require__(623))(); /***/ }), -/* 543 */ +/* 623 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -57508,7 +64378,7 @@ module.exports = new (__webpack_require__(543))(); -var MapCache = __webpack_require__(521); +var MapCache = __webpack_require__(601); /** * Create a new `FragmentCache` with an optional object to use for `caches`. @@ -57630,7 +64500,7 @@ exports = module.exports = FragmentCache; /***/ }), -/* 544 */ +/* 624 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -57643,14 +64513,14 @@ var path = __webpack_require__(16); * Module dependencies */ -var isWindows = __webpack_require__(545)(); -var Snapdragon = __webpack_require__(460); -utils.define = __webpack_require__(546); -utils.diff = __webpack_require__(547); -utils.extend = __webpack_require__(536); -utils.pick = __webpack_require__(548); -utils.typeOf = __webpack_require__(549); -utils.unique = __webpack_require__(433); +var isWindows = __webpack_require__(625)(); +var Snapdragon = __webpack_require__(540); +utils.define = __webpack_require__(626); +utils.diff = __webpack_require__(627); +utils.extend = __webpack_require__(616); +utils.pick = __webpack_require__(628); +utils.typeOf = __webpack_require__(629); +utils.unique = __webpack_require__(513); /** * Returns true if the given value is effectively an empty string @@ -58016,7 +64886,7 @@ utils.unixify = function(options) { /***/ }), -/* 545 */ +/* 625 */ /***/ (function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*! @@ -58044,7 +64914,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /***/ }), -/* 546 */ +/* 626 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -58057,8 +64927,8 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ -var isobject = __webpack_require__(440); -var isDescriptor = __webpack_require__(452); +var isobject = __webpack_require__(520); +var isDescriptor = __webpack_require__(532); var define = (typeof Reflect !== 'undefined' && Reflect.defineProperty) ? Reflect.defineProperty : Object.defineProperty; @@ -58089,7 +64959,7 @@ module.exports = function defineProperty(obj, key, val) { /***/ }), -/* 547 */ +/* 627 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -58143,7 +65013,7 @@ function diffArray(one, two) { /***/ }), -/* 548 */ +/* 628 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -58156,7 +65026,7 @@ function diffArray(one, two) { -var isObject = __webpack_require__(440); +var isObject = __webpack_require__(520); module.exports = function pick(obj, keys) { if (!isObject(obj) && typeof obj !== 'function') { @@ -58185,7 +65055,7 @@ module.exports = function pick(obj, keys) { /***/ }), -/* 549 */ +/* 629 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -58320,7 +65190,7 @@ function isBuffer(val) { /***/ }), -/* 550 */ +/* 630 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -58330,18 +65200,18 @@ function isBuffer(val) { * Module dependencies */ -var extend = __webpack_require__(430); -var unique = __webpack_require__(433); -var toRegex = __webpack_require__(421); +var extend = __webpack_require__(510); +var unique = __webpack_require__(513); +var toRegex = __webpack_require__(501); /** * Local dependencies */ -var compilers = __webpack_require__(551); -var parsers = __webpack_require__(557); -var Extglob = __webpack_require__(560); -var utils = __webpack_require__(559); +var compilers = __webpack_require__(631); +var parsers = __webpack_require__(637); +var Extglob = __webpack_require__(640); +var utils = __webpack_require__(639); var MAX_LENGTH = 1024 * 64; /** @@ -58658,13 +65528,13 @@ module.exports = extglob; /***/ }), -/* 551 */ +/* 631 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var brackets = __webpack_require__(552); +var brackets = __webpack_require__(632); /** * Extglob compilers @@ -58834,7 +65704,7 @@ module.exports = function(extglob) { /***/ }), -/* 552 */ +/* 632 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -58844,17 +65714,17 @@ module.exports = function(extglob) { * Local dependencies */ -var compilers = __webpack_require__(553); -var parsers = __webpack_require__(555); +var compilers = __webpack_require__(633); +var parsers = __webpack_require__(635); /** * Module dependencies */ -var debug = __webpack_require__(493)('expand-brackets'); -var extend = __webpack_require__(430); -var Snapdragon = __webpack_require__(460); -var toRegex = __webpack_require__(421); +var debug = __webpack_require__(573)('expand-brackets'); +var extend = __webpack_require__(510); +var Snapdragon = __webpack_require__(540); +var toRegex = __webpack_require__(501); /** * Parses the given POSIX character class `pattern` and returns a @@ -59052,13 +65922,13 @@ module.exports = brackets; /***/ }), -/* 553 */ +/* 633 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var posix = __webpack_require__(554); +var posix = __webpack_require__(634); module.exports = function(brackets) { brackets.compiler @@ -59146,7 +66016,7 @@ module.exports = function(brackets) { /***/ }), -/* 554 */ +/* 634 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -59175,14 +66045,14 @@ module.exports = { /***/ }), -/* 555 */ +/* 635 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(556); -var define = __webpack_require__(422); +var utils = __webpack_require__(636); +var define = __webpack_require__(502); /** * Text regex @@ -59401,14 +66271,14 @@ module.exports.TEXT_REGEX = TEXT_REGEX; /***/ }), -/* 556 */ +/* 636 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var toRegex = __webpack_require__(421); -var regexNot = __webpack_require__(432); +var toRegex = __webpack_require__(501); +var regexNot = __webpack_require__(512); var cached; /** @@ -59442,15 +66312,15 @@ exports.createRegex = function(pattern, include) { /***/ }), -/* 557 */ +/* 637 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var brackets = __webpack_require__(552); -var define = __webpack_require__(558); -var utils = __webpack_require__(559); +var brackets = __webpack_require__(632); +var define = __webpack_require__(638); +var utils = __webpack_require__(639); /** * Characters to use in text regex (we want to "not" match @@ -59605,7 +66475,7 @@ module.exports = parsers; /***/ }), -/* 558 */ +/* 638 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -59618,7 +66488,7 @@ module.exports = parsers; -var isDescriptor = __webpack_require__(452); +var isDescriptor = __webpack_require__(532); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -59643,14 +66513,14 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 559 */ +/* 639 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var regex = __webpack_require__(432); -var Cache = __webpack_require__(543); +var regex = __webpack_require__(512); +var Cache = __webpack_require__(623); /** * Utils @@ -59719,7 +66589,7 @@ utils.createRegex = function(str) { /***/ }), -/* 560 */ +/* 640 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -59729,16 +66599,16 @@ utils.createRegex = function(str) { * Module dependencies */ -var Snapdragon = __webpack_require__(460); -var define = __webpack_require__(558); -var extend = __webpack_require__(430); +var Snapdragon = __webpack_require__(540); +var define = __webpack_require__(638); +var extend = __webpack_require__(510); /** * Local dependencies */ -var compilers = __webpack_require__(551); -var parsers = __webpack_require__(557); +var compilers = __webpack_require__(631); +var parsers = __webpack_require__(637); /** * Customize Snapdragon parser and renderer @@ -59804,16 +66674,16 @@ module.exports = Extglob; /***/ }), -/* 561 */ +/* 641 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extglob = __webpack_require__(550); -var nanomatch = __webpack_require__(535); -var regexNot = __webpack_require__(432); -var toRegex = __webpack_require__(523); +var extglob = __webpack_require__(630); +var nanomatch = __webpack_require__(615); +var regexNot = __webpack_require__(512); +var toRegex = __webpack_require__(603); var not; /** @@ -59894,14 +66764,14 @@ function textRegex(pattern) { /***/ }), -/* 562 */ +/* 642 */ /***/ (function(module, exports, __webpack_require__) { -module.exports = new (__webpack_require__(543))(); +module.exports = new (__webpack_require__(623))(); /***/ }), -/* 563 */ +/* 643 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -59914,13 +66784,13 @@ var path = __webpack_require__(16); * Module dependencies */ -var Snapdragon = __webpack_require__(460); -utils.define = __webpack_require__(530); -utils.diff = __webpack_require__(547); -utils.extend = __webpack_require__(531); -utils.pick = __webpack_require__(548); -utils.typeOf = __webpack_require__(564); -utils.unique = __webpack_require__(433); +var Snapdragon = __webpack_require__(540); +utils.define = __webpack_require__(610); +utils.diff = __webpack_require__(627); +utils.extend = __webpack_require__(611); +utils.pick = __webpack_require__(628); +utils.typeOf = __webpack_require__(644); +utils.unique = __webpack_require__(513); /** * Returns true if the platform is windows, or `path.sep` is `\\`. @@ -60217,7 +67087,7 @@ utils.unixify = function(options) { /***/ }), -/* 564 */ +/* 644 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -60352,7 +67222,7 @@ function isBuffer(val) { /***/ }), -/* 565 */ +/* 645 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -60371,9 +67241,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(566); -var reader_1 = __webpack_require__(579); -var fs_stream_1 = __webpack_require__(583); +var readdir = __webpack_require__(646); +var reader_1 = __webpack_require__(659); +var fs_stream_1 = __webpack_require__(663); var ReaderAsync = /** @class */ (function (_super) { __extends(ReaderAsync, _super); function ReaderAsync() { @@ -60434,15 +67304,15 @@ exports.default = ReaderAsync; /***/ }), -/* 566 */ +/* 646 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const readdirSync = __webpack_require__(567); -const readdirAsync = __webpack_require__(575); -const readdirStream = __webpack_require__(578); +const readdirSync = __webpack_require__(647); +const readdirAsync = __webpack_require__(655); +const readdirStream = __webpack_require__(658); module.exports = exports = readdirAsyncPath; exports.readdir = exports.readdirAsync = exports.async = readdirAsyncPath; @@ -60526,7 +67396,7 @@ function readdirStreamStat (dir, options) { /***/ }), -/* 567 */ +/* 647 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -60534,11 +67404,11 @@ function readdirStreamStat (dir, options) { module.exports = readdirSync; -const DirectoryReader = __webpack_require__(568); +const DirectoryReader = __webpack_require__(648); let syncFacade = { - fs: __webpack_require__(573), - forEach: __webpack_require__(574), + fs: __webpack_require__(653), + forEach: __webpack_require__(654), sync: true }; @@ -60567,7 +67437,7 @@ function readdirSync (dir, options, internalOptions) { /***/ }), -/* 568 */ +/* 648 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -60576,9 +67446,9 @@ function readdirSync (dir, options, internalOptions) { const Readable = __webpack_require__(28).Readable; const EventEmitter = __webpack_require__(46).EventEmitter; const path = __webpack_require__(16); -const normalizeOptions = __webpack_require__(569); -const stat = __webpack_require__(571); -const call = __webpack_require__(572); +const normalizeOptions = __webpack_require__(649); +const stat = __webpack_require__(651); +const call = __webpack_require__(652); /** * Asynchronously reads the contents of a directory and streams the results @@ -60954,14 +67824,14 @@ module.exports = DirectoryReader; /***/ }), -/* 569 */ +/* 649 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(16); -const globToRegExp = __webpack_require__(570); +const globToRegExp = __webpack_require__(650); module.exports = normalizeOptions; @@ -61138,7 +68008,7 @@ function normalizeOptions (options, internalOptions) { /***/ }), -/* 570 */ +/* 650 */ /***/ (function(module, exports) { module.exports = function (glob, opts) { @@ -61275,13 +68145,13 @@ module.exports = function (glob, opts) { /***/ }), -/* 571 */ +/* 651 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const call = __webpack_require__(572); +const call = __webpack_require__(652); module.exports = stat; @@ -61356,7 +68226,7 @@ function symlinkStat (fs, path, lstats, callback) { /***/ }), -/* 572 */ +/* 652 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61417,14 +68287,14 @@ function callOnce (fn) { /***/ }), -/* 573 */ +/* 653 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(23); -const call = __webpack_require__(572); +const call = __webpack_require__(652); /** * A facade around {@link fs.readdirSync} that allows it to be called @@ -61488,7 +68358,7 @@ exports.lstat = function (path, callback) { /***/ }), -/* 574 */ +/* 654 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61517,7 +68387,7 @@ function syncForEach (array, iterator, done) { /***/ }), -/* 575 */ +/* 655 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61525,12 +68395,12 @@ function syncForEach (array, iterator, done) { module.exports = readdirAsync; -const maybe = __webpack_require__(576); -const DirectoryReader = __webpack_require__(568); +const maybe = __webpack_require__(656); +const DirectoryReader = __webpack_require__(648); let asyncFacade = { fs: __webpack_require__(23), - forEach: __webpack_require__(577), + forEach: __webpack_require__(657), async: true }; @@ -61572,7 +68442,7 @@ function readdirAsync (dir, options, callback, internalOptions) { /***/ }), -/* 576 */ +/* 656 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61599,7 +68469,7 @@ module.exports = function maybe (cb, promise) { /***/ }), -/* 577 */ +/* 657 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61635,7 +68505,7 @@ function asyncForEach (array, iterator, done) { /***/ }), -/* 578 */ +/* 658 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61643,11 +68513,11 @@ function asyncForEach (array, iterator, done) { module.exports = readdirStream; -const DirectoryReader = __webpack_require__(568); +const DirectoryReader = __webpack_require__(648); let streamFacade = { fs: __webpack_require__(23), - forEach: __webpack_require__(577), + forEach: __webpack_require__(657), async: true }; @@ -61667,16 +68537,16 @@ function readdirStream (dir, options, internalOptions) { /***/ }), -/* 579 */ +/* 659 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var path = __webpack_require__(16); -var deep_1 = __webpack_require__(580); -var entry_1 = __webpack_require__(582); -var pathUtil = __webpack_require__(581); +var deep_1 = __webpack_require__(660); +var entry_1 = __webpack_require__(662); +var pathUtil = __webpack_require__(661); var Reader = /** @class */ (function () { function Reader(options) { this.options = options; @@ -61742,14 +68612,14 @@ exports.default = Reader; /***/ }), -/* 580 */ +/* 660 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(581); -var patternUtils = __webpack_require__(413); +var pathUtils = __webpack_require__(661); +var patternUtils = __webpack_require__(494); var DeepFilter = /** @class */ (function () { function DeepFilter(options, micromatchOptions) { this.options = options; @@ -61832,7 +68702,7 @@ exports.default = DeepFilter; /***/ }), -/* 581 */ +/* 661 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61863,14 +68733,14 @@ exports.makeAbsolute = makeAbsolute; /***/ }), -/* 582 */ +/* 662 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(581); -var patternUtils = __webpack_require__(413); +var pathUtils = __webpack_require__(661); +var patternUtils = __webpack_require__(494); var EntryFilter = /** @class */ (function () { function EntryFilter(options, micromatchOptions) { this.options = options; @@ -61955,7 +68825,7 @@ exports.default = EntryFilter; /***/ }), -/* 583 */ +/* 663 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61975,8 +68845,8 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var stream = __webpack_require__(28); -var fsStat = __webpack_require__(584); -var fs_1 = __webpack_require__(588); +var fsStat = __webpack_require__(664); +var fs_1 = __webpack_require__(668); var FileSystemStream = /** @class */ (function (_super) { __extends(FileSystemStream, _super); function FileSystemStream() { @@ -62026,14 +68896,14 @@ exports.default = FileSystemStream; /***/ }), -/* 584 */ +/* 664 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const optionsManager = __webpack_require__(585); -const statProvider = __webpack_require__(587); +const optionsManager = __webpack_require__(665); +const statProvider = __webpack_require__(667); /** * Asynchronous API. */ @@ -62064,13 +68934,13 @@ exports.statSync = statSync; /***/ }), -/* 585 */ +/* 665 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const fsAdapter = __webpack_require__(586); +const fsAdapter = __webpack_require__(666); function prepare(opts) { const options = Object.assign({ fs: fsAdapter.getFileSystemAdapter(opts ? opts.fs : undefined), @@ -62083,7 +68953,7 @@ exports.prepare = prepare; /***/ }), -/* 586 */ +/* 666 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62106,7 +68976,7 @@ exports.getFileSystemAdapter = getFileSystemAdapter; /***/ }), -/* 587 */ +/* 667 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62158,7 +69028,7 @@ exports.isFollowedSymlink = isFollowedSymlink; /***/ }), -/* 588 */ +/* 668 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62189,7 +69059,7 @@ exports.default = FileSystem; /***/ }), -/* 589 */ +/* 669 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62209,9 +69079,9 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var stream = __webpack_require__(28); -var readdir = __webpack_require__(566); -var reader_1 = __webpack_require__(579); -var fs_stream_1 = __webpack_require__(583); +var readdir = __webpack_require__(646); +var reader_1 = __webpack_require__(659); +var fs_stream_1 = __webpack_require__(663); var TransformStream = /** @class */ (function (_super) { __extends(TransformStream, _super); function TransformStream(reader) { @@ -62279,7 +69149,7 @@ exports.default = ReaderStream; /***/ }), -/* 590 */ +/* 670 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62298,9 +69168,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(566); -var reader_1 = __webpack_require__(579); -var fs_sync_1 = __webpack_require__(591); +var readdir = __webpack_require__(646); +var reader_1 = __webpack_require__(659); +var fs_sync_1 = __webpack_require__(671); var ReaderSync = /** @class */ (function (_super) { __extends(ReaderSync, _super); function ReaderSync() { @@ -62360,7 +69230,7 @@ exports.default = ReaderSync; /***/ }), -/* 591 */ +/* 671 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62379,8 +69249,8 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var fsStat = __webpack_require__(584); -var fs_1 = __webpack_require__(588); +var fsStat = __webpack_require__(664); +var fs_1 = __webpack_require__(668); var FileSystemSync = /** @class */ (function (_super) { __extends(FileSystemSync, _super); function FileSystemSync() { @@ -62426,7 +69296,7 @@ exports.default = FileSystemSync; /***/ }), -/* 592 */ +/* 672 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62442,13 +69312,13 @@ exports.flatten = flatten; /***/ }), -/* 593 */ +/* 673 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var merge2 = __webpack_require__(594); +var merge2 = __webpack_require__(177); /** * Merge multiple streams and propagate their errors into one stream in parallel. */ @@ -62463,127 +69333,13 @@ exports.merge = merge; /***/ }), -/* 594 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -/* - * merge2 - * https://github.com/teambition/merge2 - * - * Copyright (c) 2014-2016 Teambition - * Licensed under the MIT license. - */ -const Stream = __webpack_require__(28) -const PassThrough = Stream.PassThrough -const slice = Array.prototype.slice - -module.exports = merge2 - -function merge2 () { - const streamsQueue = [] - let merging = false - const args = slice.call(arguments) - let options = args[args.length - 1] - - if (options && !Array.isArray(options) && options.pipe == null) args.pop() - else options = {} - - const doEnd = options.end !== false - if (options.objectMode == null) options.objectMode = true - if (options.highWaterMark == null) options.highWaterMark = 64 * 1024 - const mergedStream = PassThrough(options) - - function addStream () { - for (let i = 0, len = arguments.length; i < len; i++) { - streamsQueue.push(pauseStreams(arguments[i], options)) - } - mergeStream() - return this - } - - function mergeStream () { - if (merging) return - merging = true - - let streams = streamsQueue.shift() - if (!streams) { - process.nextTick(endStream) - return - } - if (!Array.isArray(streams)) streams = [streams] - - let pipesCount = streams.length + 1 - - function next () { - if (--pipesCount > 0) return - merging = false - mergeStream() - } - - function pipe (stream) { - function onend () { - stream.removeListener('merge2UnpipeEnd', onend) - stream.removeListener('end', onend) - next() - } - // skip ended stream - if (stream._readableState.endEmitted) return next() - - stream.on('merge2UnpipeEnd', onend) - stream.on('end', onend) - stream.pipe(mergedStream, { end: false }) - // compatible for old stream - stream.resume() - } - - for (let i = 0; i < streams.length; i++) pipe(streams[i]) - - next() - } - - function endStream () { - merging = false - // emit 'queueDrain' when all streams merged. - mergedStream.emit('queueDrain') - return doEnd && mergedStream.end() - } - - mergedStream.setMaxListeners(0) - mergedStream.add = addStream - mergedStream.on('unpipe', function (stream) { - stream.emit('merge2UnpipeEnd') - }) - - if (args.length) addStream.apply(null, args) - return mergedStream -} - -// check and pause streams for pipe. -function pauseStreams (streams, options) { - if (!Array.isArray(streams)) { - // Backwards-compat with old-style streams - if (!streams._readableState && streams.pipe) streams = streams.pipe(PassThrough(options)) - if (!streams._readableState || !streams.pause || !streams.pipe) { - throw new Error('Only readable stream can be merged.') - } - streams.pause() - } else { - for (let i = 0, len = streams.length; i < len; i++) streams[i] = pauseStreams(streams[i], options) - } - return streams -} - - -/***/ }), -/* 595 */ +/* 674 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(16); -const pathType = __webpack_require__(596); +const pathType = __webpack_require__(675); const getExtensions = extensions => extensions.length > 1 ? `{${extensions.join(',')}}` : extensions[0]; @@ -62649,13 +69405,13 @@ module.exports.sync = (input, opts) => { /***/ }), -/* 596 */ +/* 675 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(23); -const pify = __webpack_require__(597); +const pify = __webpack_require__(676); function type(fn, fn2, fp) { if (typeof fp !== 'string') { @@ -62698,7 +69454,7 @@ exports.symlinkSync = typeSync.bind(null, 'lstatSync', 'isSymbolicLink'); /***/ }), -/* 597 */ +/* 676 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62789,17 +69545,17 @@ module.exports = (obj, opts) => { /***/ }), -/* 598 */ +/* 677 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(23); const path = __webpack_require__(16); -const fastGlob = __webpack_require__(409); -const gitIgnore = __webpack_require__(599); -const pify = __webpack_require__(600); -const slash = __webpack_require__(601); +const fastGlob = __webpack_require__(490); +const gitIgnore = __webpack_require__(678); +const pify = __webpack_require__(679); +const slash = __webpack_require__(680); const DEFAULT_IGNORE = [ '**/node_modules/**', @@ -62897,7 +69653,7 @@ module.exports.sync = options => { /***/ }), -/* 599 */ +/* 678 */ /***/ (function(module, exports) { // A simple implementation of make-array @@ -63366,7 +70122,7 @@ module.exports = options => new IgnoreBase(options) /***/ }), -/* 600 */ +/* 679 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63441,7 +70197,7 @@ module.exports = (input, options) => { /***/ }), -/* 601 */ +/* 680 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63459,17 +70215,17 @@ module.exports = input => { /***/ }), -/* 602 */ +/* 681 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(16); const {constants: fsConstants} = __webpack_require__(23); -const {Buffer} = __webpack_require__(603); -const CpFileError = __webpack_require__(605); -const fs = __webpack_require__(607); -const ProgressEmitter = __webpack_require__(609); +const {Buffer} = __webpack_require__(682); +const CpFileError = __webpack_require__(684); +const fs = __webpack_require__(686); +const ProgressEmitter = __webpack_require__(688); const cpFile = (source, destination, options) => { if (!source || !destination) { @@ -63623,11 +70379,11 @@ module.exports.sync = (source, destination, options) => { /***/ }), -/* 603 */ +/* 682 */ /***/ (function(module, exports, __webpack_require__) { /* eslint-disable node/no-deprecated-api */ -var buffer = __webpack_require__(604) +var buffer = __webpack_require__(683) var Buffer = buffer.Buffer // alternative to using Object.keys for old browsers @@ -63691,18 +70447,18 @@ SafeBuffer.allocUnsafeSlow = function (size) { /***/ }), -/* 604 */ +/* 683 */ /***/ (function(module, exports) { module.exports = require("buffer"); /***/ }), -/* 605 */ +/* 684 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(606); +const NestedError = __webpack_require__(685); class CpFileError extends NestedError { constructor(message, nested) { @@ -63716,7 +70472,7 @@ module.exports = CpFileError; /***/ }), -/* 606 */ +/* 685 */ /***/ (function(module, exports, __webpack_require__) { var inherits = __webpack_require__(44); @@ -63770,15 +70526,15 @@ module.exports = NestedError; /***/ }), -/* 607 */ +/* 686 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(22); const makeDir = __webpack_require__(115); -const pify = __webpack_require__(608); -const CpFileError = __webpack_require__(605); +const pify = __webpack_require__(687); +const CpFileError = __webpack_require__(684); const fsP = pify(fs); @@ -63923,7 +70679,7 @@ if (fs.copyFileSync) { /***/ }), -/* 608 */ +/* 687 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63998,7 +70754,7 @@ module.exports = (input, options) => { /***/ }), -/* 609 */ +/* 688 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64039,12 +70795,12 @@ module.exports = ProgressEmitter; /***/ }), -/* 610 */ +/* 689 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(611); +const NestedError = __webpack_require__(690); class CpyError extends NestedError { constructor(message, nested) { @@ -64058,7 +70814,7 @@ module.exports = CpyError; /***/ }), -/* 611 */ +/* 690 */ /***/ (function(module, exports, __webpack_require__) { var inherits = __webpack_require__(29).inherits; @@ -64114,7 +70870,7 @@ module.exports = NestedError; /***/ }), -/* 612 */ +/* 691 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; diff --git a/packages/kbn-pm/package.json b/packages/kbn-pm/package.json index 8480e74ceb3a2..dafe73ec7d1d6 100644 --- a/packages/kbn-pm/package.json +++ b/packages/kbn-pm/package.json @@ -18,7 +18,6 @@ "@types/cmd-shim": "^2.0.0", "@types/cpy": "^5.1.0", "@types/dedent": "^0.7.0", - "@types/execa": "^0.9.0", "@types/getopts": "^2.0.1", "@types/glob": "^5.0.35", "@types/globby": "^6.1.0", @@ -40,8 +39,8 @@ "cmd-shim": "^2.1.0", "cpy": "^7.3.0", "dedent": "^0.7.0", - "del": "^4.1.1", - "execa": "^1.0.0", + "del": "^5.1.0", + "execa": "^3.2.0", "getopts": "^2.2.4", "glob": "^7.1.2", "globby": "^8.0.1", diff --git a/packages/kbn-pm/src/utils/child_process.ts b/packages/kbn-pm/src/utils/child_process.ts index 9b4b7b2516f1c..784446924a8dc 100644 --- a/packages/kbn-pm/src/utils/child_process.ts +++ b/packages/kbn-pm/src/utils/child_process.ts @@ -34,6 +34,7 @@ function generateColors() { export function spawn(command: string, args: string[], opts: execa.Options) { return execa(command, args, { stdio: 'inherit', + preferLocal: true, ...opts, }); } @@ -48,6 +49,7 @@ export function spawnStreaming( ) { const spawned = execa(command, args, { stdio: ['ignore', 'pipe', 'pipe'], + preferLocal: true, ...opts, }); diff --git a/packages/kbn-pm/src/utils/projects.test.ts b/packages/kbn-pm/src/utils/projects.test.ts index 1b839440b9449..093f178f1813a 100644 --- a/packages/kbn-pm/src/utils/projects.test.ts +++ b/packages/kbn-pm/src/utils/projects.test.ts @@ -19,7 +19,7 @@ import { mkdir, symlink } from 'fs'; import { join, resolve } from 'path'; -import rmdir from 'rimraf'; +import del from 'del'; import { promisify } from 'util'; import { getProjectPaths } from '../config'; @@ -33,20 +33,20 @@ import { topologicallyBatchProjects, } from './projects'; -const rootPath = resolve(`${__dirname}/__fixtures__/kibana`); +const rootPath = resolve(__dirname, '__fixtures__/kibana'); const rootPlugins = join(rootPath, 'plugins'); describe('#getProjects', () => { beforeAll(async () => { await promisify(mkdir)(rootPlugins); - return promisify(symlink)( + await promisify(symlink)( join(__dirname, '__fixtures__/symlinked-plugins/corge'), join(rootPlugins, 'corge') ); }); - afterAll(() => promisify(rmdir)(rootPlugins)); + afterAll(async () => await del(rootPlugins)); test('find all packages in the packages directory', async () => { const projects = await getProjects(rootPath, ['packages/*']); diff --git a/packages/kbn-test/package.json b/packages/kbn-test/package.json index 86a81207a9fa9..113783f66bc59 100644 --- a/packages/kbn-test/package.json +++ b/packages/kbn-test/package.json @@ -20,7 +20,7 @@ "dependencies": { "chalk": "^2.4.2", "dedent": "^0.7.0", - "del": "^4.1.1", + "del": "^5.1.0", "getopts": "^2.2.4", "glob": "^7.1.2", "parse-link-header": "^1.0.1", diff --git a/packages/kbn-ui-framework/package.json b/packages/kbn-ui-framework/package.json index cb4d2e3cadf95..d034e4393f58f 100644 --- a/packages/kbn-ui-framework/package.json +++ b/packages/kbn-ui-framework/package.json @@ -52,7 +52,7 @@ "imports-loader": "^0.8.0", "jquery": "^3.4.1", "keymirror": "0.1.1", - "moment": "^2.20.1", + "moment": "^2.24.0", "node-sass": "^4.9.4", "postcss": "^7.0.5", "postcss-loader": "^3.0.0", diff --git a/packages/kbn-utility-types/package.json b/packages/kbn-utility-types/package.json index b665fa20ff1ff..a79d08677020b 100644 --- a/packages/kbn-utility-types/package.json +++ b/packages/kbn-utility-types/package.json @@ -10,12 +10,13 @@ "kbn:bootstrap": "tsc", "kbn:watch": "tsc --watch", "test": "tsd", - "clean": "rimraf target" + "clean": "del target" }, "dependencies": { "utility-types": "^3.7.0" }, "devDependencies": { + "del-cli": "^3.0.0", "tsd": "^0.7.4" } } diff --git a/renovate.json5 b/renovate.json5 index 21af4ad1e83f8..0c288bb85c72c 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -353,14 +353,6 @@ '@types/elasticsearch', ], }, - { - groupSlug: 'execa', - groupName: 'execa related packages', - packageNames: [ - 'execa', - '@types/execa', - ], - }, { groupSlug: 'fetch-mock', groupName: 'fetch-mock related packages', @@ -409,14 +401,6 @@ '@types/history', ], }, - { - groupSlug: 'humps', - groupName: 'humps related packages', - packageNames: [ - 'humps', - '@types/humps', - ], - }, { groupSlug: 'jquery', groupName: 'jquery related packages', @@ -537,14 +521,6 @@ '@types/request', ], }, - { - groupSlug: 'rimraf', - groupName: 'rimraf related packages', - packageNames: [ - 'rimraf', - '@types/rimraf', - ], - }, { groupSlug: 'selenium-webdriver', groupName: 'selenium-webdriver related packages', @@ -593,6 +569,14 @@ '@types/supertest', ], }, + { + groupSlug: 'supertest-as-promised', + groupName: 'supertest-as-promised related packages', + packageNames: [ + 'supertest-as-promised', + '@types/supertest-as-promised', + ], + }, { groupSlug: 'type-detect', groupName: 'type-detect related packages', diff --git a/src/cli/cluster/cluster_manager.js b/src/cli/cluster/cluster_manager.js index 8d56f6406ef59..a67593c02a593 100644 --- a/src/cli/cluster/cluster_manager.js +++ b/src/cli/cluster/cluster_manager.js @@ -113,7 +113,7 @@ export default class ClusterManager { ...scanDirs, ]; - const extraIgnores = scanDirs + const pluginInternalDirsIgnore = scanDirs .map(scanDir => resolve(scanDir, '*')) .concat(pluginPaths) .reduce( @@ -124,16 +124,11 @@ export default class ClusterManager { resolve(path, 'target'), resolve(path, 'scripts'), resolve(path, 'docs'), - resolve(path, 'src/legacy/server/sass/__tmp__'), - resolve(path, 'legacy/plugins/reporting/.chromium'), - resolve(path, 'legacy/plugins/siem/cypress'), - resolve(path, 'legacy/plugins/apm/cypress'), - resolve(path, 'x-pack/legacy/plugins/canvas/canvas_plugin_src') // prevents server from restarting twice for Canvas plugin changes ), [] ); - this.setupWatching(extraPaths, extraIgnores); + this.setupWatching(extraPaths, pluginInternalDirsIgnore); } else this.startCluster(); } @@ -170,7 +165,7 @@ export default class ClusterManager { .then(() => opn(openUrl)); } - setupWatching(extraPaths, extraIgnores) { + setupWatching(extraPaths, pluginInternalDirsIgnore) { const chokidar = require('chokidar'); const { fromRoot } = require('../../legacy/utils'); @@ -187,12 +182,21 @@ export default class ClusterManager { ...extraPaths, ].map(path => resolve(path)); + const ignorePaths = [ + fromRoot('src/legacy/server/sass/__tmp__'), + fromRoot('x-pack/legacy/plugins/reporting/.chromium'), + fromRoot('x-pack/legacy/plugins/siem/cypress'), + fromRoot('x-pack/legacy/plugins/apm/cypress'), + fromRoot('x-pack/legacy/plugins/canvas/canvas_plugin_src') // prevents server from restarting twice for Canvas plugin changes + ]; + this.watcher = chokidar.watch(uniq(watchPaths), { cwd: fromRoot('.'), ignored: [ /[\\\/](\..*|node_modules|bower_components|public|__[a-z0-9_]+__|coverage)[\\\/]/, /\.test\.(js|ts)$/, - ...extraIgnores, + ...pluginInternalDirsIgnore, + ...ignorePaths, 'plugins/java_languageserver' ], }); diff --git a/src/cli/serve/integration_tests/reload_logging_config.test.js b/src/cli/serve/integration_tests/reload_logging_config.test.js index 8e8bdb15abc68..8558a5eb6c910 100644 --- a/src/cli/serve/integration_tests/reload_logging_config.test.js +++ b/src/cli/serve/integration_tests/reload_logging_config.test.js @@ -21,7 +21,7 @@ import { spawn } from 'child_process'; import fs from 'fs'; import path from 'path'; import os from 'os'; -import rimraf from 'rimraf'; +import del from 'del'; import { safeDump } from 'js-yaml'; import { createMapStream, createSplitStream, createPromiseFromStreams } from '../../../legacy/utils/streams'; @@ -70,7 +70,7 @@ describe('Server logging configuration', function () { child = undefined; } - rimraf.sync(tempDir); + del.sync(tempDir, { force: true }); }); const isWindows = /^win/.test(process.platform); diff --git a/src/cli_plugin/install/cleanup.js b/src/cli_plugin/install/cleanup.js index 631257fba7271..55251e6a0a0db 100644 --- a/src/cli_plugin/install/cleanup.js +++ b/src/cli_plugin/install/cleanup.js @@ -17,7 +17,7 @@ * under the License. */ -import rimraf from 'rimraf'; +import del from 'del'; import fs from 'fs'; export function cleanPrevious(settings, logger) { @@ -27,7 +27,7 @@ export function cleanPrevious(settings, logger) { logger.log('Found previous install attempt. Deleting...'); try { - rimraf.sync(settings.workingPath); + del.sync(settings.workingPath); } catch (e) { reject(e); } @@ -44,8 +44,8 @@ export function cleanArtifacts(settings) { // delete the working directory. // At this point we're bailing, so swallow any errors on delete. try { - rimraf.sync(settings.workingPath); - rimraf.sync(settings.plugins[0].path); + del.sync(settings.workingPath); + del.sync(settings.plugins[0].path); } catch (e) {} // eslint-disable-line no-empty } diff --git a/src/cli_plugin/install/cleanup.test.js b/src/cli_plugin/install/cleanup.test.js index a0f5f5064102c..bb2771920b39c 100644 --- a/src/cli_plugin/install/cleanup.test.js +++ b/src/cli_plugin/install/cleanup.test.js @@ -19,7 +19,7 @@ import sinon from 'sinon'; import fs from 'fs'; -import rimraf from 'rimraf'; +import del from 'del'; import { cleanPrevious, cleanArtifacts } from './cleanup'; import Logger from '../lib/logger'; @@ -48,11 +48,11 @@ describe('kibana cli', function () { logger.log.restore(); logger.error.restore(); fs.statSync.restore(); - rimraf.sync.restore(); + del.sync.restore(); }); it('should resolve if the working path does not exist', function () { - sinon.stub(rimraf, 'sync'); + sinon.stub(del, 'sync'); sinon.stub(fs, 'statSync').callsFake(() => { const error = new Error('ENOENT'); error.code = 'ENOENT'; @@ -67,7 +67,7 @@ describe('kibana cli', function () { }); it('should rethrow any exception except ENOENT from fs.statSync', function () { - sinon.stub(rimraf, 'sync'); + sinon.stub(del, 'sync'); sinon.stub(fs, 'statSync').throws(new Error('An Unhandled Error')); errorStub = sinon.stub(); @@ -79,7 +79,7 @@ describe('kibana cli', function () { }); it('should log a message if there was a working directory', function () { - sinon.stub(rimraf, 'sync'); + sinon.stub(del, 'sync'); sinon.stub(fs, 'statSync'); return cleanPrevious(settings, logger) @@ -89,9 +89,9 @@ describe('kibana cli', function () { }); }); - it('should rethrow any exception from rimraf.sync', function () { + it('should rethrow any exception from del.sync', function () { sinon.stub(fs, 'statSync'); - sinon.stub(rimraf, 'sync').throws(new Error('I am an error thrown by rimraf')); + sinon.stub(del, 'sync').throws(new Error('I am an error thrown by del')); errorStub = sinon.stub(); return cleanPrevious(settings, logger) @@ -102,7 +102,7 @@ describe('kibana cli', function () { }); it('should resolve if the working path is deleted', function () { - sinon.stub(rimraf, 'sync'); + sinon.stub(del, 'sync'); sinon.stub(fs, 'statSync'); return cleanPrevious(settings, logger) @@ -117,18 +117,18 @@ describe('kibana cli', function () { beforeEach(function () {}); afterEach(function () { - rimraf.sync.restore(); + del.sync.restore(); }); it('should attempt to delete the working directory', function () { - sinon.stub(rimraf, 'sync'); + sinon.stub(del, 'sync'); cleanArtifacts(settings); - expect(rimraf.sync.calledWith(settings.workingPath)).toBe(true); + expect(del.sync.calledWith(settings.workingPath)).toBe(true); }); - it('should swallow any errors thrown by rimraf.sync', function () { - sinon.stub(rimraf, 'sync').throws(new Error('Something bad happened.')); + 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.test.js b/src/cli_plugin/install/download.test.js index 9de6491f3c957..0769e5351a5d2 100644 --- a/src/cli_plugin/install/download.test.js +++ b/src/cli_plugin/install/download.test.js @@ -20,7 +20,7 @@ import sinon from 'sinon'; import nock from 'nock'; import glob from 'glob-all'; -import rimraf from 'rimraf'; +import del from 'del'; import Fs from 'fs'; import Logger from '../lib/logger'; import { UnsupportedProtocolError } from '../lib/errors'; @@ -63,14 +63,14 @@ describe('kibana cli', function () { beforeEach(function () { sinon.stub(logger, 'log'); sinon.stub(logger, 'error'); - rimraf.sync(testWorkingPath); + del.sync(testWorkingPath); Fs.mkdirSync(testWorkingPath, { recursive: true }); }); afterEach(function () { logger.log.restore(); logger.error.restore(); - rimraf.sync(testWorkingPath); + del.sync(testWorkingPath); }); describe('_downloadSingle', function () { diff --git a/src/cli_plugin/install/install.js b/src/cli_plugin/install/install.js index 10816591f7d71..a62d8525f2495 100644 --- a/src/cli_plugin/install/install.js +++ b/src/cli_plugin/install/install.js @@ -25,7 +25,7 @@ import path from 'path'; import { cleanPrevious, cleanArtifacts } from './cleanup'; import { extract, getPackData } from './pack'; import { renamePlugin } from './rename'; -import { sync as rimrafSync } from 'rimraf'; +import del from 'del'; import { errorIfXPackInstall } from '../lib/error_if_x_pack'; import { existingInstall, assertVersion } from './kibana'; import { prepareExternalProjectDependencies } from '@kbn/pm'; @@ -46,7 +46,7 @@ export default async function install(settings, logger) { await extract(settings, logger); - rimrafSync(settings.tempArchiveFile); + del.sync(settings.tempArchiveFile); existingInstall(settings, logger); diff --git a/src/cli_plugin/install/kibana.test.js b/src/cli_plugin/install/kibana.test.js index 64de96b34e807..f5485846274ce 100644 --- a/src/cli_plugin/install/kibana.test.js +++ b/src/cli_plugin/install/kibana.test.js @@ -17,19 +17,19 @@ * under the License. */ -jest.mock('fs', () => ({ - statSync: jest.fn().mockImplementation(() => require('fs').statSync), - unlinkSync: jest.fn().mockImplementation(() => require('fs').unlinkSync), - mkdirSync: jest.fn().mockImplementation(() => require('fs').mkdirSync), -})); - import sinon from 'sinon'; import Logger from '../lib/logger'; import { join } from 'path'; -import rimraf from 'rimraf'; +import del from 'del'; import fs from 'fs'; import { existingInstall, assertVersion } from './kibana'; +jest.spyOn(fs, 'statSync'); + +beforeEach(() => { + jest.clearAllMocks(); +}); + describe('kibana cli', function () { describe('plugin installer', function () { @@ -53,7 +53,7 @@ describe('kibana cli', function () { describe('assertVersion', function () { beforeEach(function () { - rimraf.sync(testWorkingPath); + del.sync(testWorkingPath); fs.mkdirSync(testWorkingPath, { recursive: true }); sinon.stub(logger, 'log'); sinon.stub(logger, 'error'); @@ -62,7 +62,7 @@ describe('kibana cli', function () { afterEach(function () { logger.log.restore(); logger.error.restore(); - rimraf.sync(testWorkingPath); + del.sync(testWorkingPath); }); it('should succeed with exact match', function () { @@ -124,14 +124,14 @@ describe('kibana cli', function () { }); it('should throw an error if the plugin already exists.', function () { - fs.statSync = jest.fn().mockImplementationOnce(() => true); + 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 () { - fs.statSync = jest.fn().mockImplementationOnce(() => { + fs.statSync.mockImplementationOnce(() => { throw { code: 'ENOENT' }; }); existingInstall(settings, logger); diff --git a/src/cli_plugin/install/pack.test.js b/src/cli_plugin/install/pack.test.js index 1bc2504397fb5..b599934b1d99b 100644 --- a/src/cli_plugin/install/pack.test.js +++ b/src/cli_plugin/install/pack.test.js @@ -21,7 +21,7 @@ import Fs from 'fs'; import sinon from 'sinon'; import glob from 'glob-all'; -import rimraf from 'rimraf'; +import del from 'del'; import Logger from '../lib/logger'; import { extract, getPackData } from './pack'; import { _downloadSingle } from './download'; @@ -41,7 +41,7 @@ describe('kibana cli', function () { beforeEach(function () { //These tests are dependent on the file system, and I had some inconsistent - //behavior with rimraf.sync show up. Until these tests are re-written to not + //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 //working directory. testNum += 1; @@ -65,7 +65,7 @@ describe('kibana cli', function () { afterEach(function () { logger.log.restore(); logger.error.restore(); - rimraf.sync(workingPathRoot); + del.sync(workingPathRoot); }); function copyReplyFile(filename) { diff --git a/src/cli_plugin/install/zip.test.js b/src/cli_plugin/install/zip.test.js index 340dec196eef5..0bcc1c182ff0d 100644 --- a/src/cli_plugin/install/zip.test.js +++ b/src/cli_plugin/install/zip.test.js @@ -17,7 +17,7 @@ * under the License. */ -import rimraf from 'rimraf'; +import del from 'del'; import path from 'path'; import os from 'os'; import glob from 'glob'; @@ -38,7 +38,7 @@ describe('kibana cli', function () { }); afterEach(() => { - rimraf.sync(tempPath); + del.sync(tempPath, { force: true }); }); describe('analyzeArchive', function () { diff --git a/src/cli_plugin/list/list.test.js b/src/cli_plugin/list/list.test.js index 425a6bee5394e..5a40fcc793e08 100644 --- a/src/cli_plugin/list/list.test.js +++ b/src/cli_plugin/list/list.test.js @@ -18,7 +18,7 @@ */ import sinon from 'sinon'; -import rimraf from 'rimraf'; +import del from 'del'; import Logger from '../lib/logger'; import list from './list'; import { join } from 'path'; @@ -46,14 +46,14 @@ describe('kibana cli', function () { logger = new Logger(settings); sinon.stub(logger, 'log'); sinon.stub(logger, 'error'); - rimraf.sync(pluginDir); + del.sync(pluginDir); mkdirSync(pluginDir, { recursive: true }); }); afterEach(function () { logger.log.restore(); logger.error.restore(); - rimraf.sync(pluginDir); + del.sync(pluginDir); }); it('list all of the folders in the plugin folder', function () { diff --git a/src/cli_plugin/remove/remove.js b/src/cli_plugin/remove/remove.js index 7aaccd8ca05fe..8432d0f44836b 100644 --- a/src/cli_plugin/remove/remove.js +++ b/src/cli_plugin/remove/remove.js @@ -20,7 +20,7 @@ import { statSync } from 'fs'; import { errorIfXPackRemove } from '../lib/error_if_x_pack'; -import rimraf from 'rimraf'; +import del from 'del'; export default function remove(settings, logger) { try { @@ -37,7 +37,7 @@ export default function remove(settings, logger) { } logger.log(`Removing ${settings.plugin}...`); - rimraf.sync(settings.pluginPath); + del.sync(settings.pluginPath); logger.log('Plugin removal complete'); } catch (err) { logger.error(`Unable to remove plugin because of error: "${err.message}"`); diff --git a/src/cli_plugin/remove/remove.test.js b/src/cli_plugin/remove/remove.test.js index 5d936d0278521..c063a713f47ac 100644 --- a/src/cli_plugin/remove/remove.test.js +++ b/src/cli_plugin/remove/remove.test.js @@ -19,7 +19,7 @@ import sinon from 'sinon'; import glob from 'glob-all'; -import rimraf from 'rimraf'; +import del from 'del'; import Logger from '../lib/logger'; import remove from './remove'; import { join } from 'path'; @@ -40,7 +40,7 @@ describe('kibana cli', function () { logger = new Logger(settings); sinon.stub(logger, 'log'); sinon.stub(logger, 'error'); - rimraf.sync(pluginDir); + del.sync(pluginDir); mkdirSync(pluginDir, { recursive: true }); }); @@ -48,7 +48,7 @@ describe('kibana cli', function () { processExitStub.restore(); logger.log.restore(); logger.error.restore(); - rimraf.sync(pluginDir); + del.sync(pluginDir); }); it('throw an error if the plugin is not installed.', function () { diff --git a/src/core/MIGRATION.md b/src/core/MIGRATION.md index 36883440e0b5d..c26a383719fa1 100644 --- a/src/core/MIGRATION.md +++ b/src/core/MIGRATION.md @@ -174,10 +174,10 @@ In the new platform, there are three lifecycle functions today: `setup`, `start` The table below explains how each lifecycle event relates to the state of Kibana. | lifecycle event | server | browser | -|-----------------|-------------------------------------------|-----------------------------------------------------| +| --------------- | ----------------------------------------- | --------------------------------------------------- | | *setup* | bootstrapping and configuring routes | loading plugin bundles and configuring applications | | *start* | server is now serving traffic | browser is now showing UI to the user | -| *stop* | server has received a request to shutdown | user is navigating away from Kibana | +| *stop* | server has received a request to shutdown | user is navigating away from Kibana | There is no equivalent behavior to `start` or `stop` in legacy plugins, so this guide primarily focuses on migrating functionality into `setup`. @@ -1096,7 +1096,7 @@ import { npStart: { core } } from 'ui/new_platform'; ``` | Legacy Platform | New Platform | Notes | -|-------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------| +| ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- | | `chrome.addBasePath` | [`core.http.basePath.prepend`](/docs/development/core/public/kibana-plugin-public.httpservicebase.basepath.md) | | | `chrome.breadcrumbs.set` | [`core.chrome.setBreadcrumbs`](/docs/development/core/public/kibana-plugin-public.chromestart.setbreadcrumbs.md) | | | `chrome.getUiSettingsClient` | [`core.uiSettings`](/docs/development/core/public/kibana-plugin-public.uisettingsclient.md) | | @@ -1111,6 +1111,7 @@ import { npStart: { core } } from 'ui/new_platform'; | `ui/notify` | [`core.notifications`](/docs/development/core/public/kibana-plugin-public.notificationsstart.md) and [`core.overlays`](/docs/development/core/public/kibana-plugin-public.overlaystart.md) | Toast messages are in `notifications`, banners are in `overlays`. May be combined later. | | `ui/routes` | -- | There is no global routing mechanism. Each app [configures its own routing](/rfcs/text/0004_application_service_mounting.md#complete-example). | | `ui/saved_objects` | [`core.savedObjects`](/docs/development/core/public/kibana-plugin-public.savedobjectsstart.md) | Client API is the same | +| `ui/doc_title` | [`core.chrome.docTitle`](/docs/development/core/public/kibana-plugin-public.chromedoctitle.md) | | _See also: [Public's CoreStart API Docs](/docs/development/core/public/kibana-plugin-public.corestart.md)_ @@ -1125,24 +1126,25 @@ import { setup, start } from '../core_plugins/embeddables/public/legacy'; import { setup, start } from '../core_plugins/visualizations/public/legacy'; ``` -| Legacy Platform | New Platform | Notes | -|--------------------------------------------------------|--------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------| -| `import 'ui/apply_filters'` | `import { ApplyFiltersPopover } from '../data/public'` | `import '../data/public/legacy` should be called to load legacy directives | -| `import 'ui/filter_bar'` | `import { FilterBar } from '../data/public'` | `import '../data/public/legacy` should be called to load legacy directives | -| `import 'ui/query_bar'` | `import { QueryBar, QueryBarInput } from '../data/public'` | Directives are deprecated. | -| `import 'ui/search_bar'` | `import { SearchBar } from '../data/public'` | Directive is deprecated. | -| `import 'ui/kbn_top_nav'` | `import { TopNavMenu } from '../kibana_react/public'` | Directive is still available in `ui/kbn_top_nav`. | -| `core_plugins/interpreter` | `data.expressions` | still in progress | -| `ui/courier` | `data.search` | still in progress | -| `ui/embeddable` | `embeddables` | still in progress | -| `ui/filter_manager` | `data.filter` | -- | -| `ui/index_patterns` | `data.indexPatterns` | still in progress | -| `ui/registry/vis_types` | `visualizations.types` | -- | -| `ui/vis` | `visualizations.types` | -- | -| `ui/vis/vis_factory` | `visualizations.types` | -- | -| `ui/vis/vis_filters` | `visualizations.filters` | -- | -| `ui/utils/parse_es_interval` | `import { parseEsInterval } from '../data/public'` | `parseEsInterval`, `ParsedInterval`, `InvalidEsCalendarIntervalError`, `InvalidEsIntervalFormatError` items were moved to the `Data Plugin` as a static code | - +| Legacy Platform | New Platform | Notes | +| ------------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `import 'ui/apply_filters'` | `import { ApplyFiltersPopover } from '../data/public'` | `import '../data/public/legacy` should be called to load legacy directives | +| `import 'ui/filter_bar'` | `import { FilterBar } from '../data/public'` | `import '../data/public/legacy` should be called to load legacy directives | +| `import 'ui/query_bar'` | `import { QueryBar, QueryBarInput } from '../data/public'` | Directives are deprecated. | +| `import 'ui/search_bar'` | `import { SearchBar } from '../data/public'` | Directive is deprecated. | +| `import 'ui/kbn_top_nav'` | `import { TopNavMenu } from '../navigation/public'` | Directive is still available in `ui/kbn_top_nav`. | +| `ui/saved_objects/components/saved_object_finder` | `import { SavedObjectFinder } from '../kibana_react/public'` | | +| `core_plugins/interpreter` | `data.expressions` | still in progress | +| `ui/courier` | `data.search` | still in progress | +| `ui/embeddable` | `embeddables` | still in progress | +| `ui/filter_manager` | `data.filter` | -- | +| `ui/index_patterns` | `data.indexPatterns` | still in progress | +| `ui/registry/feature_catalogue | `feature_catalogue.register` | Must add `feature_catalogue` as a dependency in your kibana.json. | +| `ui/registry/vis_types` | `visualizations.types` | -- | +| `ui/vis` | `visualizations.types` | -- | +| `ui/vis/vis_factory` | `visualizations.types` | -- | +| `ui/vis/vis_filters` | `visualizations.filters` | -- | +| `ui/utils/parse_es_interval` | `import { parseEsInterval } from '../data/public'` | `parseEsInterval`, `ParsedInterval`, `InvalidEsCalendarIntervalError`, `InvalidEsIntervalFormatError` items were moved to the `Data Plugin` as a static code | #### Server-side @@ -1150,9 +1152,9 @@ import { setup, start } from '../core_plugins/visualizations/public/legacy'; In server code, `core` can be accessed from either `server.newPlatform` or `kbnServer.newPlatform`. There are not currently very many services available on the server-side: | Legacy Platform | New Platform | Notes | -|----------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------| +| -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | | `server.config()` | [`initializerContext.config.create()`](/docs/development/core/server/kibana-plugin-server.plugininitializercontext.config.md) | Must also define schema. See _[how to configure plugin](#configure-plugin)_ | -| `server.route` | [`core.http.createRouter`](/docs/development/core/server/kibana-plugin-server.httpservicesetup.createrouter.md) | [Examples](./MIGRATION_EXAMPLES.md#route-registration) | +| `server.route` | [`core.http.createRouter`](/docs/development/core/server/kibana-plugin-server.httpservicesetup.createrouter.md) | [Examples](./MIGRATION_EXAMPLES.md#route-registration) | | `request.getBasePath()` | [`core.http.basePath.get`](/docs/development/core/server/kibana-plugin-server.httpservicesetup.basepath.md) | | | `server.plugins.elasticsearch.getCluster('data')` | [`core.elasticsearch.dataClient$`](/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.dataclient_.md) | Handlers will also include a pre-configured client | | `server.plugins.elasticsearch.getCluster('admin')` | [`core.elasticsearch.adminClient$`](/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.adminclient_.md) | Handlers will also include a pre-configured client | @@ -1166,7 +1168,7 @@ The legacy platform uses a set of "uiExports" to inject modules from one plugin This table shows where these uiExports have moved to in the New Platform. In most cases, if a uiExport you need is not yet available in the New Platform, you may leave in your legacy plugin for the time being and continue to migrate the rest of your app to the New Platform. | Legacy Platform | New Platform | Notes | -|------------------------------|---------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------| +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | | `aliases` | | | | `app` | [`core.application.register`](/docs/development/core/public/kibana-plugin-public.applicationsetup.register.md) | | | `canvas` | | Should be an API on the canvas plugin. | @@ -1179,7 +1181,7 @@ This table shows where these uiExports have moved to in the New Platform. In mos | `fieldFormatEditors` | | | | `fieldFormats` | | | | `hacks` | n/a | Just run the code in your plugin's `start` method. | -| `home` | | Should be an API on the home plugin. | +| `home` | [`plugins.feature_catalogue.register`](./src/plugins/feature_catalogue) | Must add `feature_catalogue` as a dependency in your kibana.json. | | `indexManagement` | | Should be an API on the indexManagement plugin. | | `injectDefaultVars` | n/a | Plugins will only be able to "whitelist" config values for the frontend. See [#41990](https://github.com/elastic/kibana/issues/41990) | | `inspectorViews` | | Should be an API on the data (?) plugin. | @@ -1197,7 +1199,7 @@ This table shows where these uiExports have moved to in the New Platform. In mos | `styleSheetPaths` | | | | `taskDefinitions` | | Should be an API on the taskManager plugin. | | `uiCapabilities` | [`core.application.register`](/docs/development/core/public/kibana-plugin-public.applicationsetup.register.md) | | -| `uiSettingDefaults` | | Most likely part of server-side UiSettingsService. | +| `uiSettingDefaults` | [`core.uiSettings.register`](/docs/development/core/server/kibana-plugin-server.uisettingsservicesetup.md) | | | `validations` | | Part of SavedObjects, see [#33587](https://github.com/elastic/kibana/issues/33587) | | `visEditorTypes` | | | | `visTypeEnhancers` | | | diff --git a/src/core/public/chrome/chrome_service.mock.ts b/src/core/public/chrome/chrome_service.mock.ts index 3775989c5126b..6f61ee9dc21ba 100644 --- a/src/core/public/chrome/chrome_service.mock.ts +++ b/src/core/public/chrome/chrome_service.mock.ts @@ -43,6 +43,13 @@ const createStartContractMock = () => { get: jest.fn(), get$: jest.fn(), }, + docTitle: { + change: jest.fn(), + reset: jest.fn(), + __legacy: { + setBaseTitle: jest.fn(), + }, + }, navControls: { registerLeft: jest.fn(), registerRight: jest.fn(), diff --git a/src/core/public/chrome/chrome_service.tsx b/src/core/public/chrome/chrome_service.tsx index 71279ad6fed03..87389d2c10f03 100644 --- a/src/core/public/chrome/chrome_service.tsx +++ b/src/core/public/chrome/chrome_service.tsx @@ -33,10 +33,11 @@ import { HttpStart } from '../http'; import { ChromeNavLinks, NavLinksService } from './nav_links'; import { ChromeRecentlyAccessed, RecentlyAccessedService } from './recently_accessed'; import { NavControlsService, ChromeNavControls } from './nav_controls'; +import { DocTitleService, ChromeDocTitle } from './doc_title'; import { LoadingIndicator, HeaderWrapper as Header } from './ui'; import { DocLinksStart } from '../doc_links'; -export { ChromeNavControls, ChromeRecentlyAccessed }; +export { ChromeNavControls, ChromeRecentlyAccessed, ChromeDocTitle }; const IS_COLLAPSED_KEY = 'core.chrome.isCollapsed'; @@ -82,6 +83,7 @@ export class ChromeService { private readonly navControls = new NavControlsService(); private readonly navLinks = new NavLinksService(); private readonly recentlyAccessed = new RecentlyAccessedService(); + private readonly docTitle = new DocTitleService(); constructor(private readonly params: ConstructorParams) {} @@ -106,6 +108,7 @@ export class ChromeService { const navControls = this.navControls.start(); const navLinks = this.navLinks.start({ application, http }); const recentlyAccessed = await this.recentlyAccessed.start({ http }); + const docTitle = this.docTitle.start({ document: window.document }); if (!this.params.browserSupportsCsp && injectedMetadata.getCspConfig().warnLegacyBrowsers) { notifications.toasts.addWarning( @@ -119,6 +122,7 @@ export class ChromeService { navControls, navLinks, recentlyAccessed, + docTitle, getHeaderComponent: () => ( @@ -259,6 +263,8 @@ export interface ChromeStart { navControls: ChromeNavControls; /** {@inheritdoc ChromeRecentlyAccessed} */ recentlyAccessed: ChromeRecentlyAccessed; + /** {@inheritdoc ChromeDocTitle} */ + docTitle: ChromeDocTitle; /** * Sets the current app's title diff --git a/src/core/public/chrome/doc_title/doc_title_service.test.ts b/src/core/public/chrome/doc_title/doc_title_service.test.ts new file mode 100644 index 0000000000000..763e8c9ebd74a --- /dev/null +++ b/src/core/public/chrome/doc_title/doc_title_service.test.ts @@ -0,0 +1,80 @@ +/* + * 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 { DocTitleService } from './doc_title_service'; + +describe('DocTitleService', () => { + const defaultTitle = 'KibanaTest'; + const document = { title: '' }; + + const getStart = (title: string = defaultTitle) => { + document.title = title; + return new DocTitleService().start({ document }); + }; + + beforeEach(() => { + document.title = defaultTitle; + }); + + describe('#change()', () => { + it('changes the title of the document', async () => { + getStart().change('TitleA'); + expect(document.title).toEqual('TitleA - KibanaTest'); + }); + + it('appends the baseTitle to the title', async () => { + const start = getStart('BaseTitle'); + start.change('TitleA'); + expect(document.title).toEqual('TitleA - BaseTitle'); + start.change('TitleB'); + expect(document.title).toEqual('TitleB - BaseTitle'); + }); + + it('accepts string arrays as input', async () => { + const start = getStart(); + start.change(['partA', 'partB']); + expect(document.title).toEqual(`partA - partB - ${defaultTitle}`); + start.change(['partA', 'partB', 'partC']); + expect(document.title).toEqual(`partA - partB - partC - ${defaultTitle}`); + }); + }); + + describe('#reset()', () => { + it('resets the title to the initial value', async () => { + const start = getStart('InitialTitle'); + start.change('TitleA'); + expect(document.title).toEqual('TitleA - InitialTitle'); + start.reset(); + expect(document.title).toEqual('InitialTitle'); + }); + }); + + describe('#__legacy.setBaseTitle()', () => { + it('allows to change the baseTitle after startup', async () => { + const start = getStart('InitialTitle'); + start.change('WithInitial'); + expect(document.title).toEqual('WithInitial - InitialTitle'); + start.__legacy.setBaseTitle('NewBaseTitle'); + start.change('WithNew'); + expect(document.title).toEqual('WithNew - NewBaseTitle'); + start.reset(); + expect(document.title).toEqual('NewBaseTitle'); + }); + }); +}); diff --git a/src/core/public/chrome/doc_title/doc_title_service.ts b/src/core/public/chrome/doc_title/doc_title_service.ts new file mode 100644 index 0000000000000..9453abe54de66 --- /dev/null +++ b/src/core/public/chrome/doc_title/doc_title_service.ts @@ -0,0 +1,105 @@ +/* + * 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 { compact, flattenDeep, isString } from 'lodash'; + +interface StartDeps { + document: { title: string }; +} + +/** + * APIs for accessing and updating the document title. + * + * @example + * How to change the title of the document + * ```ts + * chrome.docTitle.change('My application') + * ``` + * + * @example + * How to reset the title of the document to it's initial value + * ```ts + * chrome.docTitle.reset() + * ``` + * + * @public + * */ +export interface ChromeDocTitle { + /** + * Changes the current document title. + * + * @example + * How to change the title of the document + * ```ts + * chrome.docTitle.change('My application title') + * chrome.docTitle.change(['My application', 'My section']) + * ``` + * + * @param newTitle The new title to set, either a string or string array + */ + change(newTitle: string | string[]): void; + /** + * Resets the document title to it's initial value. + * (meaning the one present in the title meta at application load.) + */ + reset(): void; + + /** @internal */ + __legacy: { + setBaseTitle(baseTitle: string): void; + }; +} + +const defaultTitle: string[] = []; +const titleSeparator = ' - '; + +/** @internal */ +export class DocTitleService { + private document = { title: '' }; + private baseTitle = ''; + + public start({ document }: StartDeps): ChromeDocTitle { + this.document = document; + this.baseTitle = document.title; + + return { + change: (title: string | string[]) => { + this.applyTitle(title); + }, + reset: () => { + this.applyTitle(defaultTitle); + }, + __legacy: { + setBaseTitle: baseTitle => { + this.baseTitle = baseTitle; + }, + }, + }; + } + + private applyTitle(title: string | string[]) { + this.document.title = this.render(title); + } + + private render(title: string | string[]) { + const parts = [...(isString(title) ? [title] : title), this.baseTitle]; + // ensuring compat with legacy that might be passing nested arrays + return compact(flattenDeep(parts)).join(titleSeparator); + } +} diff --git a/src/legacy/ui/public/styles/disable_animations/index.js b/src/core/public/chrome/doc_title/index.ts similarity index 95% rename from src/legacy/ui/public/styles/disable_animations/index.js rename to src/core/public/chrome/doc_title/index.ts index 2c459667d5996..b070d01953f7a 100644 --- a/src/legacy/ui/public/styles/disable_animations/index.js +++ b/src/core/public/chrome/doc_title/index.ts @@ -17,4 +17,4 @@ * under the License. */ -import './disable_animations'; +export * from './doc_title_service'; diff --git a/src/core/public/chrome/index.ts b/src/core/public/chrome/index.ts index 6e03f9e023983..b220a81f775f8 100644 --- a/src/core/public/chrome/index.ts +++ b/src/core/public/chrome/index.ts @@ -29,3 +29,4 @@ export { export { ChromeNavLink, ChromeNavLinks, ChromeNavLinkUpdateableFields } from './nav_links'; export { ChromeRecentlyAccessed, ChromeRecentlyAccessedHistoryItem } from './recently_accessed'; export { ChromeNavControl, ChromeNavControls } from './nav_controls'; +export { ChromeDocTitle } from './doc_title'; diff --git a/src/core/public/core_system.test.mocks.ts b/src/core/public/core_system.test.mocks.ts index d2494badfacdb..75ab6cdb628f7 100644 --- a/src/core/public/core_system.test.mocks.ts +++ b/src/core/public/core_system.test.mocks.ts @@ -31,6 +31,7 @@ import { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock'; import { docLinksServiceMock } from './doc_links/doc_links_service.mock'; import { renderingServiceMock } from './rendering/rendering_service.mock'; import { contextServiceMock } from './context/context_service.mock'; +import { integrationsServiceMock } from './integrations/integrations_service.mock'; export const MockLegacyPlatformService = legacyPlatformServiceMock.create(); export const LegacyPlatformServiceConstructor = jest @@ -127,3 +128,11 @@ export const ContextServiceConstructor = jest.fn().mockImplementation(() => Mock jest.doMock('./context', () => ({ ContextService: ContextServiceConstructor, })); + +export const MockIntegrationsService = integrationsServiceMock.create(); +export const IntegrationsServiceConstructor = jest + .fn() + .mockImplementation(() => MockIntegrationsService); +jest.doMock('./integrations', () => ({ + IntegrationsService: IntegrationsServiceConstructor, +})); diff --git a/src/core/public/core_system.test.ts b/src/core/public/core_system.test.ts index ec6f020a3490b..d78504a899a34 100644 --- a/src/core/public/core_system.test.ts +++ b/src/core/public/core_system.test.ts @@ -42,6 +42,8 @@ import { MockRenderingService, RenderingServiceConstructor, MockContextService, + IntegrationsServiceConstructor, + MockIntegrationsService, } from './core_system.test.mocks'; import { CoreSystem } from './core_system'; @@ -86,6 +88,7 @@ describe('constructor', () => { expect(ChromeServiceConstructor).toHaveBeenCalledTimes(1); expect(OverlayServiceConstructor).toHaveBeenCalledTimes(1); expect(RenderingServiceConstructor).toHaveBeenCalledTimes(1); + expect(IntegrationsServiceConstructor).toHaveBeenCalledTimes(1); }); it('passes injectedMetadata param to InjectedMetadataService', () => { @@ -213,6 +216,11 @@ describe('#setup()', () => { await setupCore(); expect(MockPluginsService.setup).toHaveBeenCalledTimes(1); }); + + it('calls integrations#setup()', async () => { + await setupCore(); + expect(MockIntegrationsService.setup).toHaveBeenCalledTimes(1); + }); }); describe('#start()', () => { @@ -296,6 +304,11 @@ describe('#start()', () => { targetDomElement: expect.any(HTMLElement), }); }); + + it('calls start#setup()', async () => { + await startCore(); + expect(MockIntegrationsService.start).toHaveBeenCalledTimes(1); + }); }); describe('#stop()', () => { @@ -346,6 +359,14 @@ describe('#stop()', () => { expect(MockI18nService.stop).toHaveBeenCalled(); }); + it('calls integrations.stop()', () => { + const coreSystem = createCoreSystem(); + + expect(MockIntegrationsService.stop).not.toHaveBeenCalled(); + coreSystem.stop(); + expect(MockIntegrationsService.stop).toHaveBeenCalled(); + }); + it('clears the rootDomElement', async () => { const rootDomElement = document.createElement('div'); const coreSystem = createCoreSystem({ diff --git a/src/core/public/core_system.ts b/src/core/public/core_system.ts index 71e9c39df7aae..2404459ad1383 100644 --- a/src/core/public/core_system.ts +++ b/src/core/public/core_system.ts @@ -41,8 +41,9 @@ import { ApplicationService } from './application'; import { mapToObject, pick } from '../utils/'; import { DocLinksService } from './doc_links'; import { RenderingService } from './rendering'; -import { SavedObjectsService } from './saved_objects/saved_objects_service'; +import { SavedObjectsService } from './saved_objects'; import { ContextService } from './context'; +import { IntegrationsService } from './integrations'; import { InternalApplicationSetup, InternalApplicationStart } from './application/types'; interface Params { @@ -98,6 +99,7 @@ export class CoreSystem { private readonly docLinks: DocLinksService; private readonly rendering: RenderingService; private readonly context: ContextService; + private readonly integrations: IntegrationsService; private readonly rootDomElement: HTMLElement; private readonly coreContext: CoreContext; @@ -134,6 +136,7 @@ export class CoreSystem { this.docLinks = new DocLinksService(); this.rendering = new RenderingService(); this.application = new ApplicationService(); + this.integrations = new IntegrationsService(); this.coreContext = { coreId: Symbol('core'), env: injectedMetadata.env }; @@ -155,6 +158,7 @@ export class CoreSystem { injectedMetadata, i18n: this.i18n.getContext(), }); + await this.integrations.setup(); const http = this.http.setup({ injectedMetadata, fatalErrors: this.fatalErrorsSetup }); const uiSettings = this.uiSettings.setup({ http, injectedMetadata }); const notifications = this.notifications.setup({ uiSettings }); @@ -211,6 +215,7 @@ export class CoreSystem { const savedObjects = await this.savedObjects.start({ http }); const i18n = await this.i18n.start(); const application = await this.application.start({ http, injectedMetadata }); + await this.integrations.start({ uiSettings }); const coreUiTargetDomElement = document.createElement('div'); coreUiTargetDomElement.id = 'kibana-body'; @@ -297,6 +302,7 @@ export class CoreSystem { this.plugins.stop(); this.notifications.stop(); this.http.stop(); + this.integrations.stop(); this.uiSettings.stop(); this.chrome.stop(); this.i18n.stop(); diff --git a/src/core/public/http/anonymous_paths.test.ts b/src/core/public/http/anonymous_paths.test.ts new file mode 100644 index 0000000000000..bf9212f625f1e --- /dev/null +++ b/src/core/public/http/anonymous_paths.test.ts @@ -0,0 +1,107 @@ +/* + * 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 { AnonymousPaths } from './anonymous_paths'; +import { BasePath } from './base_path_service'; + +describe('#register', () => { + it(`allows paths that don't start with /`, () => { + const basePath = new BasePath('/foo'); + const anonymousPaths = new AnonymousPaths(basePath); + anonymousPaths.register('bar'); + }); + + it(`allows paths that end with '/'`, () => { + const basePath = new BasePath('/foo'); + const anonymousPaths = new AnonymousPaths(basePath); + anonymousPaths.register('/bar/'); + }); +}); + +describe('#isAnonymous', () => { + it('returns true for registered paths', () => { + const basePath = new BasePath('/foo'); + const anonymousPaths = new AnonymousPaths(basePath); + anonymousPaths.register('/bar'); + expect(anonymousPaths.isAnonymous('/foo/bar')).toBe(true); + }); + + it('returns true for paths registered with a trailing slash, but call "isAnonymous" with no trailing slash', () => { + const basePath = new BasePath('/foo'); + const anonymousPaths = new AnonymousPaths(basePath); + anonymousPaths.register('/bar/'); + expect(anonymousPaths.isAnonymous('/foo/bar')).toBe(true); + }); + + it('returns true for paths registered without a trailing slash, but call "isAnonymous" with a trailing slash', () => { + const basePath = new BasePath('/foo'); + const anonymousPaths = new AnonymousPaths(basePath); + anonymousPaths.register('/bar'); + expect(anonymousPaths.isAnonymous('/foo/bar/')).toBe(true); + }); + + it('returns true for paths registered without a starting slash', () => { + const basePath = new BasePath('/foo'); + const anonymousPaths = new AnonymousPaths(basePath); + anonymousPaths.register('bar'); + expect(anonymousPaths.isAnonymous('/foo/bar')).toBe(true); + }); + + it('returns true for paths registered with a starting slash', () => { + const basePath = new BasePath('/foo'); + const anonymousPaths = new AnonymousPaths(basePath); + anonymousPaths.register('/bar'); + expect(anonymousPaths.isAnonymous('/foo/bar')).toBe(true); + }); + + it('when there is no basePath and calling "isAnonymous" without a starting slash, returns true for paths registered with a starting slash', () => { + const basePath = new BasePath('/'); + const anonymousPaths = new AnonymousPaths(basePath); + anonymousPaths.register('/bar'); + expect(anonymousPaths.isAnonymous('bar')).toBe(true); + }); + + it('when there is no basePath and calling "isAnonymous" with a starting slash, returns true for paths registered with a starting slash', () => { + const basePath = new BasePath('/'); + const anonymousPaths = new AnonymousPaths(basePath); + anonymousPaths.register('/bar'); + expect(anonymousPaths.isAnonymous('/bar')).toBe(true); + }); + + it('returns true for paths whose capitalization is different', () => { + const basePath = new BasePath('/foo'); + const anonymousPaths = new AnonymousPaths(basePath); + anonymousPaths.register('/BAR'); + expect(anonymousPaths.isAnonymous('/foo/bar')).toBe(true); + }); + + it('returns false for other paths', () => { + const basePath = new BasePath('/foo'); + const anonymousPaths = new AnonymousPaths(basePath); + anonymousPaths.register('/bar'); + expect(anonymousPaths.isAnonymous('/foo/foo')).toBe(false); + }); + + it('returns false for sub-paths of registered paths', () => { + const basePath = new BasePath('/foo'); + const anonymousPaths = new AnonymousPaths(basePath); + anonymousPaths.register('/bar'); + expect(anonymousPaths.isAnonymous('/foo/bar/baz')).toBe(false); + }); +}); diff --git a/src/core/public/http/anonymous_paths.ts b/src/core/public/http/anonymous_paths.ts new file mode 100644 index 0000000000000..300c4d64df353 --- /dev/null +++ b/src/core/public/http/anonymous_paths.ts @@ -0,0 +1,53 @@ +/* + * 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 { IAnonymousPaths, IBasePath } from 'src/core/public'; + +export class AnonymousPaths implements IAnonymousPaths { + private readonly paths = new Set(); + + constructor(private basePath: IBasePath) {} + + public isAnonymous(path: string): boolean { + const pathWithoutBasePath = this.basePath.remove(path); + return this.paths.has(this.normalizePath(pathWithoutBasePath)); + } + + public register(path: string) { + this.paths.add(this.normalizePath(path)); + } + + private normalizePath(path: string) { + // always lower-case it + let normalized = path.toLowerCase(); + + // remove the slash from the end + if (normalized.endsWith('/')) { + normalized = normalized.slice(0, normalized.length - 1); + } + + // put a slash at the start + if (!normalized.startsWith('/')) { + normalized = `/${normalized}`; + } + + // it's normalized!!! + return normalized; + } +} diff --git a/src/core/public/http/http_service.mock.ts b/src/core/public/http/http_service.mock.ts index a94543414acfa..52f188c7b20a0 100644 --- a/src/core/public/http/http_service.mock.ts +++ b/src/core/public/http/http_service.mock.ts @@ -20,9 +20,11 @@ import { HttpService } from './http_service'; import { HttpSetup } from './types'; import { BehaviorSubject } from 'rxjs'; +import { BasePath } from './base_path_service'; +import { AnonymousPaths } from './anonymous_paths'; type ServiceSetupMockType = jest.Mocked & { - basePath: jest.Mocked; + basePath: BasePath; }; const createServiceMock = ({ basePath = '' } = {}): ServiceSetupMockType => ({ @@ -34,11 +36,8 @@ const createServiceMock = ({ basePath = '' } = {}): ServiceSetupMockType => ({ patch: jest.fn(), delete: jest.fn(), options: jest.fn(), - basePath: { - get: jest.fn(() => basePath), - prepend: jest.fn(path => `${basePath}${path}`), - remove: jest.fn(), - }, + basePath: new BasePath(basePath), + anonymousPaths: new AnonymousPaths(new BasePath(basePath)), addLoadingCount: jest.fn(), getLoadingCount$: jest.fn().mockReturnValue(new BehaviorSubject(0)), stop: jest.fn(), diff --git a/src/core/public/http/http_setup.ts b/src/core/public/http/http_setup.ts index a10358926de1f..602382e3a5a60 100644 --- a/src/core/public/http/http_setup.ts +++ b/src/core/public/http/http_setup.ts @@ -36,6 +36,7 @@ import { HttpInterceptController } from './http_intercept_controller'; import { HttpFetchError } from './http_fetch_error'; import { HttpInterceptHaltError } from './http_intercept_halt_error'; import { BasePath } from './base_path_service'; +import { AnonymousPaths } from './anonymous_paths'; const JSON_CONTENT = /^(application\/(json|x-javascript)|text\/(x-)?javascript|x-json)(;.*)?$/; const NDJSON_CONTENT = /^(application\/ndjson)(;.*)?$/; @@ -57,6 +58,7 @@ export const setup = ( const interceptors = new Set(); const kibanaVersion = injectedMetadata.getKibanaVersion(); const basePath = new BasePath(injectedMetadata.getBasePath()); + const anonymousPaths = new AnonymousPaths(basePath); function intercept(interceptor: HttpInterceptor) { interceptors.add(interceptor); @@ -318,6 +320,7 @@ export const setup = ( return { stop, basePath, + anonymousPaths, intercept, removeAllInterceptors, fetch, diff --git a/src/core/public/http/types.ts b/src/core/public/http/types.ts index 96500d566b3e5..870d4af8f9e86 100644 --- a/src/core/public/http/types.ts +++ b/src/core/public/http/types.ts @@ -29,6 +29,11 @@ export interface HttpServiceBase { */ basePath: IBasePath; + /** + * APIs for denoting certain paths for not requiring authentication + */ + anonymousPaths: IAnonymousPaths; + /** * Adds a new {@link HttpInterceptor} to the global HTTP client. * @param interceptor a {@link HttpInterceptor} @@ -92,6 +97,21 @@ export interface IBasePath { remove: (url: string) => string; } +/** + * APIs for denoting paths as not requiring authentication + */ +export interface IAnonymousPaths { + /** + * Determines whether the provided path doesn't require authentication. `path` should include the current basePath. + */ + isAnonymous(path: string): boolean; + + /** + * Register `path` as not requiring authentication. `path` should not include the current basePath. + */ + register(path: string): void; +} + /** * See {@link HttpServiceBase} * @public diff --git a/src/core/public/index.ts b/src/core/public/index.ts index 3d8714a001158..7391cf7f9454c 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -45,6 +45,7 @@ import { ChromeNavLink, ChromeNavLinks, ChromeNavLinkUpdateableFields, + ChromeDocTitle, ChromeStart, ChromeRecentlyAccessed, ChromeRecentlyAccessedHistoryItem, @@ -110,6 +111,7 @@ export { HttpHandler, HttpBody, IBasePath, + IAnonymousPaths, IHttpInterceptController, IHttpFetchError, InterceptedHttpResponse, @@ -249,6 +251,7 @@ export { ChromeNavLink, ChromeNavLinks, ChromeNavLinkUpdateableFields, + ChromeDocTitle, ChromeRecentlyAccessed, ChromeRecentlyAccessedHistoryItem, ChromeStart, diff --git a/src/core/public/injected_metadata/injected_metadata_service.ts b/src/core/public/injected_metadata/injected_metadata_service.ts index 478285a70771e..a5342aaa48b72 100644 --- a/src/core/public/injected_metadata/injected_metadata_service.ts +++ b/src/core/public/injected_metadata/injected_metadata_service.ts @@ -19,8 +19,12 @@ import { get } from 'lodash'; import { DiscoveredPlugin, PluginName } from '../../server'; -import { EnvironmentMode, PackageInfo } from '../../server/types'; -import { UiSettingsState } from '../ui_settings'; +import { + EnvironmentMode, + PackageInfo, + UiSettingsParams, + UserProvidedValues, +} from '../../server/types'; import { deepFreeze } from '../../utils/'; import { Capabilities } from '..'; @@ -69,8 +73,8 @@ export interface InjectedMetadataParams { serverName: string; devMode: boolean; uiSettings: { - defaults: UiSettingsState; - user?: UiSettingsState; + defaults: Record; + user?: Record; }; }; }; @@ -179,8 +183,8 @@ export interface InjectedMetadataSetup { serverName: string; devMode: boolean; uiSettings: { - defaults: UiSettingsState; - user?: UiSettingsState | undefined; + defaults: Record; + user?: Record | undefined; }; }; getInjectedVar: (name: string, defaultValue?: any) => unknown; diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/index.ts b/src/core/public/integrations/index.ts similarity index 92% rename from src/legacy/core_plugins/kibana/public/discover/doc_viewer/index.ts rename to src/core/public/integrations/index.ts index 8ce94f24128df..efa9456093359 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/index.ts +++ b/src/core/public/integrations/index.ts @@ -17,6 +17,4 @@ * under the License. */ -import './doc_viewer_directive'; - -export * from './doc_viewer'; +export { IntegrationsService } from './integrations_service'; diff --git a/src/core/public/integrations/integrations_service.mock.ts b/src/core/public/integrations/integrations_service.mock.ts new file mode 100644 index 0000000000000..4f6ca0fb68459 --- /dev/null +++ b/src/core/public/integrations/integrations_service.mock.ts @@ -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 { IntegrationsService } from './integrations_service'; + +type IntegrationsServiceContract = PublicMethodsOf; +const createMock = (): jest.Mocked => ({ + setup: jest.fn(), + start: jest.fn(), + stop: jest.fn(), +}); + +export const integrationsServiceMock = { + create: createMock, +}; diff --git a/src/core/public/integrations/integrations_service.test.mocks.ts b/src/core/public/integrations/integrations_service.test.mocks.ts new file mode 100644 index 0000000000000..82535724565a7 --- /dev/null +++ b/src/core/public/integrations/integrations_service.test.mocks.ts @@ -0,0 +1,38 @@ +/* + * 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 { CoreService } from '../../types'; + +const createCoreServiceMock = (): jest.Mocked => { + return { + setup: jest.fn(), + start: jest.fn(), + stop: jest.fn(), + }; +}; + +export const styleServiceMock = createCoreServiceMock(); +jest.doMock('./styles', () => ({ + StylesService: jest.fn(() => styleServiceMock), +})); + +export const momentServiceMock = createCoreServiceMock(); +jest.doMock('./moment', () => ({ + MomentService: jest.fn(() => momentServiceMock), +})); diff --git a/src/core/public/integrations/integrations_service.test.ts b/src/core/public/integrations/integrations_service.test.ts new file mode 100644 index 0000000000000..03784c92e12ab --- /dev/null +++ b/src/core/public/integrations/integrations_service.test.ts @@ -0,0 +1,42 @@ +/* + * 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 { styleServiceMock, momentServiceMock } from './integrations_service.test.mocks'; + +import { IntegrationsService } from './integrations_service'; +import { uiSettingsServiceMock } from '../ui_settings/ui_settings_service.mock'; + +describe('IntegrationsService', () => { + test('it wires up styles and moment', async () => { + const uiSettings = uiSettingsServiceMock.createStartContract(); + const service = new IntegrationsService(); + + await service.setup(); + expect(styleServiceMock.setup).toHaveBeenCalledWith(); + expect(momentServiceMock.setup).toHaveBeenCalledWith(); + + await service.start({ uiSettings }); + expect(styleServiceMock.start).toHaveBeenCalledWith({ uiSettings }); + expect(momentServiceMock.start).toHaveBeenCalledWith({ uiSettings }); + + await service.stop(); + expect(styleServiceMock.stop).toHaveBeenCalledWith(); + expect(momentServiceMock.stop).toHaveBeenCalledWith(); + }); +}); diff --git a/src/core/public/integrations/integrations_service.ts b/src/core/public/integrations/integrations_service.ts new file mode 100644 index 0000000000000..5d5c31c2df18c --- /dev/null +++ b/src/core/public/integrations/integrations_service.ts @@ -0,0 +1,49 @@ +/* + * 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 { UiSettingsClientContract } from '../ui_settings'; +import { CoreService } from '../../types'; + +import { MomentService } from './moment'; +import { StylesService } from './styles'; + +interface Deps { + uiSettings: UiSettingsClientContract; +} + +/** @internal */ +export class IntegrationsService implements CoreService { + private readonly styles = new StylesService(); + private readonly moment = new MomentService(); + + public async setup() { + await this.styles.setup(); + await this.moment.setup(); + } + + public async start({ uiSettings }: Deps) { + await this.styles.start({ uiSettings }); + await this.moment.start({ uiSettings }); + } + + public async stop() { + await this.styles.stop(); + await this.moment.stop(); + } +} diff --git a/src/core/public/integrations/moment/index.ts b/src/core/public/integrations/moment/index.ts new file mode 100644 index 0000000000000..9c7c140bff64d --- /dev/null +++ b/src/core/public/integrations/moment/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 { MomentService } from './moment_service'; diff --git a/src/core/public/integrations/moment/moment_service.test.mocks.ts b/src/core/public/integrations/moment/moment_service.test.mocks.ts new file mode 100644 index 0000000000000..2fa013a6bf132 --- /dev/null +++ b/src/core/public/integrations/moment/moment_service.test.mocks.ts @@ -0,0 +1,28 @@ +/* + * 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 momentMock = { + locale: jest.fn(() => 'default-locale'), + tz: { + setDefault: jest.fn(), + }, + weekdays: jest.fn(() => ['dow1', 'dow2', 'dow3']), + updateLocale: jest.fn(), +}; +jest.doMock('moment-timezone', () => momentMock); diff --git a/src/core/public/integrations/moment/moment_service.test.ts b/src/core/public/integrations/moment/moment_service.test.ts new file mode 100644 index 0000000000000..c9b4479f2f163 --- /dev/null +++ b/src/core/public/integrations/moment/moment_service.test.ts @@ -0,0 +1,71 @@ +/* + * 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 { momentMock } from './moment_service.test.mocks'; +import { MomentService } from './moment_service'; +import { uiSettingsServiceMock } from '../../ui_settings/ui_settings_service.mock'; +import { BehaviorSubject } from 'rxjs'; + +describe('MomentService', () => { + let service: MomentService; + beforeEach(() => { + momentMock.tz.setDefault.mockClear(); + momentMock.weekdays.mockClear(); + momentMock.updateLocale.mockClear(); + service = new MomentService(); + }); + afterEach(() => service.stop()); + + const flushPromises = () => new Promise(resolve => setTimeout(resolve, 100)); + + test('sets initial moment config', async () => { + const tz$ = new BehaviorSubject('tz1'); + const dow$ = new BehaviorSubject('dow1'); + + const uiSettings = uiSettingsServiceMock.createSetupContract(); + uiSettings.get$.mockReturnValueOnce(tz$).mockReturnValueOnce(dow$); + + service.start({ uiSettings }); + await flushPromises(); + expect(momentMock.tz.setDefault).toHaveBeenCalledWith('tz1'); + expect(momentMock.updateLocale).toHaveBeenCalledWith('default-locale', { week: { dow: 0 } }); + }); + + test('updates moment config', async () => { + const tz$ = new BehaviorSubject('tz1'); + const dow$ = new BehaviorSubject('dow1'); + + const uiSettings = uiSettingsServiceMock.createSetupContract(); + uiSettings.get$.mockReturnValueOnce(tz$).mockReturnValueOnce(dow$); + + service.start({ uiSettings }); + tz$.next('tz2'); + tz$.next('tz3'); + dow$.next('dow3'); + dow$.next('dow2'); + + await flushPromises(); + expect(momentMock.tz.setDefault.mock.calls).toEqual([['tz1'], ['tz2'], ['tz3']]); + expect(momentMock.updateLocale.mock.calls).toEqual([ + ['default-locale', { week: { dow: 0 } }], + ['default-locale', { week: { dow: 2 } }], + ['default-locale', { week: { dow: 1 } }], + ]); + }); +}); diff --git a/src/core/public/integrations/moment/moment_service.ts b/src/core/public/integrations/moment/moment_service.ts new file mode 100644 index 0000000000000..2714750d9a65e --- /dev/null +++ b/src/core/public/integrations/moment/moment_service.ts @@ -0,0 +1,56 @@ +/* + * 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 moment from 'moment-timezone'; +import { merge, Subscription } from 'rxjs'; + +import { tap } from 'rxjs/operators'; +import { UiSettingsClientContract } from '../../ui_settings'; +import { CoreService } from '../../../types'; + +interface StartDeps { + uiSettings: UiSettingsClientContract; +} + +/** @internal */ +export class MomentService implements CoreService { + private uiSettingsSubscription?: Subscription; + + public async setup() {} + + public async start({ uiSettings }: StartDeps) { + const setDefaultTimezone = (tz: string) => moment.tz.setDefault(tz); + const setStartDayOfWeek = (day: string) => { + const dow = moment.weekdays().indexOf(day); + moment.updateLocale(moment.locale(), { week: { dow } } as any); + }; + + this.uiSettingsSubscription = merge( + uiSettings.get$('dateFormat:tz').pipe(tap(setDefaultTimezone)), + uiSettings.get$('dateFormat:dow').pipe(tap(setStartDayOfWeek)) + ).subscribe(); + } + + public async stop() { + if (this.uiSettingsSubscription) { + this.uiSettingsSubscription.unsubscribe(); + this.uiSettingsSubscription = undefined; + } + } +} diff --git a/src/legacy/ui/public/styles/disable_animations/disable_animations.css b/src/core/public/integrations/styles/disable_animations.css similarity index 100% rename from src/legacy/ui/public/styles/disable_animations/disable_animations.css rename to src/core/public/integrations/styles/disable_animations.css diff --git a/src/core/public/integrations/styles/index.ts b/src/core/public/integrations/styles/index.ts new file mode 100644 index 0000000000000..6021eea8ee10a --- /dev/null +++ b/src/core/public/integrations/styles/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 { StylesService } from './styles_service'; diff --git a/src/core/public/integrations/styles/styles_service.test.ts b/src/core/public/integrations/styles/styles_service.test.ts new file mode 100644 index 0000000000000..e413e9cc2f4d7 --- /dev/null +++ b/src/core/public/integrations/styles/styles_service.test.ts @@ -0,0 +1,63 @@ +/* + * 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 { BehaviorSubject } from 'rxjs'; + +jest.mock('!!raw-loader!./disable_animations.css', () => 'MOCK DISABLE ANIMATIONS CSS'); + +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 getDisableAnimationsTag = () => document.querySelector('style#disableAnimationsCss')!; + + afterEach(() => getDisableAnimationsTag().remove()); + + test('sets initial disable animations style', async () => { + const disableAnimations$ = new BehaviorSubject(false); + + const uiSettings = uiSettingsServiceMock.createSetupContract(); + uiSettings.get$.mockReturnValueOnce(disableAnimations$); + + new StylesService().start({ uiSettings }); + await flushPromises(); + + const styleTag = getDisableAnimationsTag(); + expect(styleTag).toBeDefined(); + expect(styleTag.textContent).toEqual(''); + }); + + test('updates disable animations style', async () => { + const disableAnimations$ = new BehaviorSubject(false); + + const uiSettings = uiSettingsServiceMock.createSetupContract(); + uiSettings.get$.mockReturnValueOnce(disableAnimations$); + + new StylesService().start({ uiSettings }); + + disableAnimations$.next(true); + await flushPromises(); + expect(getDisableAnimationsTag().textContent).toEqual('MOCK DISABLE ANIMATIONS CSS'); + + disableAnimations$.next(false); + await flushPromises(); + expect(getDisableAnimationsTag().textContent).toEqual(''); + }); +}); diff --git a/src/core/public/integrations/styles/styles_service.ts b/src/core/public/integrations/styles/styles_service.ts new file mode 100644 index 0000000000000..ba8b812fe9988 --- /dev/null +++ b/src/core/public/integrations/styles/styles_service.ts @@ -0,0 +1,57 @@ +/* + * 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 { Subscription } from 'rxjs'; + +import { UiSettingsClientContract } from '../../ui_settings'; +import { CoreService } from '../../../types'; +// @ts-ignore +import disableAnimationsCss from '!!raw-loader!./disable_animations.css'; + +interface StartDeps { + uiSettings: UiSettingsClientContract; +} + +/** @internal */ +export class StylesService implements CoreService { + private uiSettingsSubscription?: Subscription; + + public async setup() {} + + public async start({ uiSettings }: StartDeps) { + const disableAnimationsStyleTag = document.createElement('style'); + disableAnimationsStyleTag.setAttribute('id', 'disableAnimationsCss'); + document.head.appendChild(disableAnimationsStyleTag); + + const setDisableAnimations = (disableAnimations: boolean) => { + disableAnimationsStyleTag.textContent = disableAnimations ? disableAnimationsCss : ''; + }; + + this.uiSettingsSubscription = uiSettings + .get$('accessibility:disableAnimations') + .subscribe(setDisableAnimations); + } + + public async stop() { + if (this.uiSettingsSubscription) { + this.uiSettingsSubscription.unsubscribe(); + this.uiSettingsSubscription = undefined; + } + } +} diff --git a/src/core/public/mocks.ts b/src/core/public/mocks.ts index 8345980b6869d..b9cd2577c2217 100644 --- a/src/core/public/mocks.ts +++ b/src/core/public/mocks.ts @@ -18,7 +18,7 @@ */ import { applicationServiceMock } from './application/application_service.mock'; import { chromeServiceMock } from './chrome/chrome_service.mock'; -import { CoreContext, CoreSetup, CoreStart, PluginInitializerContext } from '.'; +import { CoreContext, CoreSetup, CoreStart, PluginInitializerContext, NotificationsSetup } from '.'; import { docLinksServiceMock } from './doc_links/doc_links_service.mock'; import { fatalErrorsServiceMock } from './fatal_errors/fatal_errors_service.mock'; import { httpServiceMock } from './http/http_service.mock'; @@ -41,12 +41,12 @@ export { notificationServiceMock } from './notifications/notifications_service.m export { overlayServiceMock } from './overlays/overlay_service.mock'; export { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock'; -function createCoreSetupMock() { - const mock: MockedKeys = { +function createCoreSetupMock({ basePath = '' } = {}) { + const mock: MockedKeys & { notifications: MockedKeys } = { application: applicationServiceMock.createSetupContract(), context: contextServiceMock.createSetupContract(), fatalErrors: fatalErrorsServiceMock.createSetupContract(), - http: httpServiceMock.createSetupContract(), + http: httpServiceMock.createSetupContract({ basePath }), notifications: notificationServiceMock.createSetupContract(), uiSettings: uiSettingsServiceMock.createSetupContract(), injectedMetadata: { @@ -57,12 +57,12 @@ function createCoreSetupMock() { return mock; } -function createCoreStartMock() { - const mock: MockedKeys = { +function createCoreStartMock({ basePath = '' } = {}) { + const mock: MockedKeys & { notifications: MockedKeys } = { application: applicationServiceMock.createStartContract(), chrome: chromeServiceMock.createStartContract(), docLinks: docLinksServiceMock.createStartContract(), - http: httpServiceMock.createStartContract(), + http: httpServiceMock.createStartContract({ basePath }), i18n: i18nServiceMock.createStartContract(), notifications: notificationServiceMock.createStartContract(), overlays: overlayServiceMock.createStartContract(), diff --git a/src/core/public/notifications/notifications_service.mock.ts b/src/core/public/notifications/notifications_service.mock.ts index 8f4a1d5bcd400..464f47e20aa3b 100644 --- a/src/core/public/notifications/notifications_service.mock.ts +++ b/src/core/public/notifications/notifications_service.mock.ts @@ -23,10 +23,8 @@ import { } from './notifications_service'; import { toastsServiceMock } from './toasts/toasts_service.mock'; -type DeeplyMocked = { [P in keyof T]: jest.Mocked }; - const createSetupContractMock = () => { - const setupContract: DeeplyMocked = { + const setupContract: MockedKeys = { // we have to suppress type errors until decide how to mock es6 class toasts: toastsServiceMock.createSetupContract(), }; @@ -34,7 +32,7 @@ const createSetupContractMock = () => { }; const createStartContractMock = () => { - const startContract: DeeplyMocked = { + const startContract: MockedKeys = { // we have to suppress type errors until decide how to mock es6 class toasts: toastsServiceMock.createStartContract(), }; diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index ec8a22fe5953c..a596ea394abda 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -11,6 +11,8 @@ import React from 'react'; import * as Rx from 'rxjs'; import { ShallowPromise } from '@kbn/utility-types'; import { EuiGlobalToastListToast as Toast } from '@elastic/eui'; +import { UiSettingsParams as UiSettingsParams_2 } from 'src/core/server/types'; +import { UserProvidedValues as UserProvidedValues_2 } from 'src/core/server/types'; // @public export interface App extends AppBase { @@ -105,6 +107,16 @@ export interface ChromeBrand { // @public (undocumented) export type ChromeBreadcrumb = Breadcrumb; +// @public +export interface ChromeDocTitle { + // @internal (undocumented) + __legacy: { + setBaseTitle(baseTitle: string): void; + }; + change(newTitle: string | string[]): void; + reset(): void; +} + // @public (undocumented) export type ChromeHelpExtension = (element: HTMLDivElement) => () => void; @@ -186,6 +198,7 @@ export interface ChromeRecentlyAccessedHistoryItem { // @public export interface ChromeStart { addApplicationClass(className: string): void; + docTitle: ChromeDocTitle; getApplicationClasses$(): Observable; getBadge$(): Observable; getBrand$(): Observable; @@ -488,6 +501,7 @@ export interface HttpResponse extends InterceptedHttpResponse { // @public (undocumented) export interface HttpServiceBase { addLoadingCount(countSource$: Observable): void; + anonymousPaths: IAnonymousPaths; basePath: IBasePath; delete: HttpHandler; fetch: HttpHandler; @@ -517,6 +531,14 @@ export interface I18nStart { }) => JSX.Element; } +// Warning: (ae-missing-release-tag) "IAnonymousPaths" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface IAnonymousPaths { + isAnonymous(path: string): boolean; + register(path: string): void; +} + // @public export interface IBasePath { get: () => string; @@ -937,7 +959,7 @@ export class UiSettingsClient { constructor(params: UiSettingsClientParams); get$(key: string, defaultOverride?: any): Rx.Observable; get(key: string, defaultOverride?: any): any; - getAll(): UiSettingsState; + getAll(): Record>; getSaved$(): Rx.Observable<{ key: string; newValue: any; @@ -959,16 +981,13 @@ export class UiSettingsClient { stop(): void; } -// @public (undocumented) +// @public export type UiSettingsClientContract = PublicMethodsOf; // @public (undocumented) export interface UiSettingsState { - // Warning: (ae-forgotten-export) The symbol "InjectedUiSettingsDefault" needs to be exported by the entry point index.d.ts - // Warning: (ae-forgotten-export) The symbol "InjectedUiSettingsUser" needs to be exported by the entry point index.d.ts - // // (undocumented) - [key: string]: InjectedUiSettingsDefault & InjectedUiSettingsUser; + [key: string]: UiSettingsParams_2 & UserProvidedValues_2; } diff --git a/src/core/public/saved_objects/index.ts b/src/core/public/saved_objects/index.ts index 74d33c506db48..453c3e42a1687 100644 --- a/src/core/public/saved_objects/index.ts +++ b/src/core/public/saved_objects/index.ts @@ -30,7 +30,7 @@ export { SavedObjectsBulkUpdateOptions, } from './saved_objects_client'; export { SimpleSavedObject } from './simple_saved_object'; -export { SavedObjectsStart } from './saved_objects_service'; +export { SavedObjectsStart, SavedObjectsService } from './saved_objects_service'; export { SavedObject, SavedObjectAttribute, diff --git a/src/core/public/ui_settings/__snapshots__/ui_settings_service.test.ts.snap b/src/core/public/ui_settings/__snapshots__/ui_settings_service.test.ts.snap index f607c924a9e68..84f9a5ab7c5cd 100644 --- a/src/core/public/ui_settings/__snapshots__/ui_settings_service.test.ts.snap +++ b/src/core/public/ui_settings/__snapshots__/ui_settings_service.test.ts.snap @@ -20,10 +20,20 @@ exports[`#setup constructs UiSettingsClient and UiSettingsApi: UiSettingsApi arg }, ], }, - "basePath": Object { - "get": [MockFunction], - "prepend": [MockFunction], - "remove": [MockFunction], + "anonymousPaths": AnonymousPaths { + "basePath": BasePath { + "basePath": "", + "get": [Function], + "prepend": [Function], + "remove": [Function], + }, + "paths": Set {}, + }, + "basePath": BasePath { + "basePath": "", + "get": [Function], + "prepend": [Function], + "remove": [Function], }, "delete": [MockFunction], "fetch": [MockFunction], diff --git a/src/core/public/ui_settings/types.ts b/src/core/public/ui_settings/types.ts index d1cf25c3e9e6f..24e87eb04f026 100644 --- a/src/core/public/ui_settings/types.ts +++ b/src/core/public/ui_settings/types.ts @@ -17,28 +17,9 @@ * under the License. */ -// properties that come from legacyInjectedMetadata.uiSettings.defaults -interface InjectedUiSettingsDefault { - name?: string; - value?: any; - description?: string; - category?: string[]; - type?: string; - readOnly?: boolean; - options?: string[] | { [key: string]: any }; - /** - * Whether a change in that setting will only take affect after a page reload. - */ - requiresPageReload?: boolean; -} - -// properties that come from legacyInjectedMetadata.uiSettings.user -interface InjectedUiSettingsUser { - userValue?: any; - isOverridden?: boolean; -} +import { UiSettingsParams, UserProvidedValues } from 'src/core/server/types'; /** @public */ export interface UiSettingsState { - [key: string]: InjectedUiSettingsDefault & InjectedUiSettingsUser; + [key: string]: UiSettingsParams & UserProvidedValues; } diff --git a/src/core/public/ui_settings/ui_settings_client.ts b/src/core/public/ui_settings/ui_settings_client.ts index 3083851dd453d..c3190847130d5 100644 --- a/src/core/public/ui_settings/ui_settings_client.ts +++ b/src/core/public/ui_settings/ui_settings_client.ts @@ -21,18 +21,25 @@ import { cloneDeep, defaultsDeep } from 'lodash'; import * as Rx from 'rxjs'; import { filter, map } from 'rxjs/operators'; +import { UiSettingsParams, UserProvidedValues } from 'src/core/server/types'; import { UiSettingsState } from './types'; + import { UiSettingsApi } from './ui_settings_api'; /** @public */ interface UiSettingsClientParams { api: UiSettingsApi; - defaults: UiSettingsState; + defaults: Record; initialSettings?: UiSettingsState; } /** + * 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. * {@link UiSettingsClient} + * * @public */ export type UiSettingsClientContract = PublicMethodsOf; @@ -44,8 +51,8 @@ export class UiSettingsClient { private readonly updateErrors$ = new Rx.Subject(); private readonly api: UiSettingsApi; - private readonly defaults: UiSettingsState; - private cache: UiSettingsState; + private readonly defaults: Record; + private cache: Record; constructor(params: UiSettingsClientParams) { this.api = params.api; diff --git a/src/core/server/http/router/response_adapter.ts b/src/core/server/http/router/response_adapter.ts index fe6cb32d21776..e5dabc99f4442 100644 --- a/src/core/server/http/router/response_adapter.ts +++ b/src/core/server/http/router/response_adapter.ts @@ -120,7 +120,11 @@ export class HapiResponseAdapter { }); error.output.payload.message = getErrorMessage(payload); - error.output.payload.attributes = getErrorAttributes(payload); + + const attributes = getErrorAttributes(payload); + if (attributes) { + error.output.payload.attributes = attributes; + } const headers = kibanaResponse.options.headers; if (headers) { diff --git a/src/core/server/index.ts b/src/core/server/index.ts index e0d230006d587..35e83da4ef30c 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -49,7 +49,11 @@ import { PluginsServiceSetup, PluginsServiceStart, PluginOpaqueId } from './plug import { ContextSetup } from './context'; import { SavedObjectsServiceStart } from './saved_objects'; -import { InternalUiSettingsServiceSetup } from './ui_settings'; +import { + InternalUiSettingsServiceSetup, + IUiSettingsClient, + UiSettingsServiceSetup, +} from './ui_settings'; import { SavedObjectsClientContract } from './saved_objects/types'; export { bootstrap } from './bootstrap'; @@ -139,6 +143,7 @@ export { SavedObjectsBulkCreateObject, SavedObjectsBulkGetObject, SavedObjectsBulkUpdateObject, + SavedObjectsBulkUpdateOptions, SavedObjectsBulkResponse, SavedObjectsBulkUpdateResponse, SavedObjectsClient, @@ -166,6 +171,7 @@ export { SavedObjectsLegacyService, SavedObjectsUpdateOptions, SavedObjectsUpdateResponse, + SavedObjectsDeleteOptions, } from './saved_objects'; export { @@ -173,6 +179,8 @@ export { UiSettingsParams, InternalUiSettingsServiceSetup, UiSettingsType, + UiSettingsServiceSetup, + UserProvidedValues, } from './ui_settings'; export { RecursiveReadonly } from '../utils'; @@ -184,6 +192,7 @@ export { SavedObjectAttributeSingle, SavedObjectReference, SavedObjectsBaseOptions, + MutatingOperationRefreshSetting, SavedObjectsClientContract, SavedObjectsFindOptions, SavedObjectsMigrationVersion, @@ -213,6 +222,9 @@ export interface RequestHandlerContext { dataClient: IScopedClusterClient; adminClient: IScopedClusterClient; }; + uiSettings: { + client: IUiSettingsClient; + }; }; } @@ -228,6 +240,8 @@ export interface CoreSetup { elasticsearch: ElasticsearchServiceSetup; /** {@link HttpServiceSetup} */ http: HttpServiceSetup; + /** {@link UiSettingsServiceSetup} */ + uiSettings: UiSettingsServiceSetup; } /** diff --git a/src/core/server/legacy/legacy_service.test.ts b/src/core/server/legacy/legacy_service.test.ts index 590bd192e3ded..e2aefd846d978 100644 --- a/src/core/server/legacy/legacy_service.test.ts +++ b/src/core/server/legacy/legacy_service.test.ts @@ -72,7 +72,7 @@ beforeEach(() => { core: { context: contextServiceMock.createSetupContract(), elasticsearch: { legacy: {} } as any, - uiSettings: uiSettingsServiceMock.createSetup(), + uiSettings: uiSettingsServiceMock.createSetupContract(), http: { ...httpServiceMock.createSetupContract(), auth: { diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index dd3ee3db89153..b7c55a8af7c18 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -248,6 +248,9 @@ export class LegacyService implements CoreService { basePath: setupDeps.core.http.basePath, isTlsEnabled: setupDeps.core.http.isTlsEnabled, }, + uiSettings: { + register: setupDeps.core.uiSettings.register, + }, }; const coreStart: CoreStart = {}; diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index fb703c6c35008..b51d5302e3274 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -22,6 +22,7 @@ import { loggingServiceMock } from './logging/logging_service.mock'; import { elasticsearchServiceMock } from './elasticsearch/elasticsearch_service.mock'; import { httpServiceMock } from './http/http_service.mock'; import { contextServiceMock } from './context/context_service.mock'; +import { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock'; export { httpServerMock } from './http/http_server.mocks'; export { sessionStorageMock } from './http/cookie_session_storage.mocks'; @@ -29,7 +30,7 @@ export { configServiceMock } from './config/config_service.mock'; export { elasticsearchServiceMock } from './elasticsearch/elasticsearch_service.mock'; export { httpServiceMock } from './http/http_service.mock'; export { loggingServiceMock } from './logging/logging_service.mock'; -export { SavedObjectsClientMock } from './saved_objects/service/saved_objects_client.mock'; +export { savedObjectsClientMock } from './saved_objects/service/saved_objects_client.mock'; export { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock'; export function pluginInitializerContextConfigMock(config: T) { @@ -79,10 +80,14 @@ function createCoreSetupMock() { }; httpMock.createRouter.mockImplementation(() => httpService.createRouter('')); + const uiSettingsMock = { + register: uiSettingsServiceMock.createSetupContract().register, + }; const mock: MockedKeys = { context: contextServiceMock.createSetupContract(), elasticsearch: elasticsearchServiceMock.createSetupContract(), http: httpMock, + uiSettings: uiSettingsMock, }; return mock; @@ -94,8 +99,19 @@ function createCoreStartMock() { return mock; } +function createInternalCoreSetupMock() { + const setupDeps = { + context: contextServiceMock.createSetupContract(), + elasticsearch: elasticsearchServiceMock.createSetupContract(), + http: httpServiceMock.createSetupContract(), + uiSettings: uiSettingsServiceMock.createSetupContract(), + }; + return setupDeps; +} + export const coreMock = { createSetup: createCoreSetupMock, createStart: createCoreStartMock, + createInternalSetup: createInternalCoreSetupMock, createPluginInitializerContext: pluginInitializerContextMock, }; diff --git a/src/core/server/plugins/plugin.test.ts b/src/core/server/plugins/plugin.test.ts index 5c57a5fa2c8d1..e457f01a1941c 100644 --- a/src/core/server/plugins/plugin.test.ts +++ b/src/core/server/plugins/plugin.test.ts @@ -24,15 +24,13 @@ import { schema } from '@kbn/config-schema'; import { Env } from '../config'; import { getEnvOptions } from '../config/__mocks__/env'; import { CoreContext } from '../core_context'; +import { coreMock } from '../mocks'; import { configServiceMock } from '../config/config_service.mock'; -import { elasticsearchServiceMock } from '../elasticsearch/elasticsearch_service.mock'; -import { httpServiceMock } from '../http/http_service.mock'; import { loggingServiceMock } from '../logging/logging_service.mock'; import { PluginWrapper } from './plugin'; import { PluginManifest } from './types'; import { createPluginInitializerContext, createPluginSetupContext } from './plugin_context'; -import { contextServiceMock } from '../context/context_service.mock'; const mockPluginInitializer = jest.fn(); const logger = loggingServiceMock.create(); @@ -68,11 +66,7 @@ configService.atPath.mockReturnValue(new BehaviorSubject({ initialize: true })); let coreId: symbol; let env: Env; let coreContext: CoreContext; -const setupDeps = { - context: contextServiceMock.createSetupContract(), - elasticsearch: elasticsearchServiceMock.createSetupContract(), - http: httpServiceMock.createSetupContract(), -}; +const setupDeps = coreMock.createInternalSetup(); beforeEach(() => { coreId = Symbol('core'); env = Env.createDefault(getEnvOptions()); diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index edcafbb9a3dc3..9885a572ad8c0 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -123,6 +123,9 @@ export function createPluginSetupContext( basePath: deps.http.basePath, isTlsEnabled: deps.http.isTlsEnabled, }, + uiSettings: { + register: deps.uiSettings.register, + }, }; } diff --git a/src/core/server/plugins/plugins_service.test.ts b/src/core/server/plugins/plugins_service.test.ts index d25f9087432bd..0b3bc0759463c 100644 --- a/src/core/server/plugins/plugins_service.test.ts +++ b/src/core/server/plugins/plugins_service.test.ts @@ -25,15 +25,13 @@ import { schema } from '@kbn/config-schema'; import { Config, ConfigPath, ConfigService, Env, ObjectToConfigAdapter } from '../config'; import { getEnvOptions } from '../config/__mocks__/env'; -import { elasticsearchServiceMock } from '../elasticsearch/elasticsearch_service.mock'; -import { httpServiceMock } from '../http/http_service.mock'; +import { coreMock } from '../mocks'; import { loggingServiceMock } from '../logging/logging_service.mock'; import { PluginDiscoveryError } from './discovery'; import { PluginWrapper } from './plugin'; import { PluginsService } from './plugins_service'; import { PluginsSystem } from './plugins_system'; import { config } from './plugins_config'; -import { contextServiceMock } from '../context/context_service.mock'; const MockPluginsSystem: jest.Mock = PluginsSystem as any; @@ -42,11 +40,7 @@ let configService: ConfigService; let coreId: symbol; let env: Env; let mockPluginSystem: jest.Mocked; -const setupDeps = { - context: contextServiceMock.createSetupContract(), - elasticsearch: elasticsearchServiceMock.createSetupContract(), - http: httpServiceMock.createSetupContract(), -}; +const setupDeps = coreMock.createInternalSetup(); const logger = loggingServiceMock.create(); ['path-1', 'path-2', 'path-3', 'path-4', 'path-5'].forEach(path => { diff --git a/src/core/server/plugins/plugins_service.ts b/src/core/server/plugins/plugins_service.ts index 92b537deae337..2964e34c370b1 100644 --- a/src/core/server/plugins/plugins_service.ts +++ b/src/core/server/plugins/plugins_service.ts @@ -21,15 +21,14 @@ import { Observable } from 'rxjs'; import { filter, first, map, mergeMap, tap, toArray } from 'rxjs/operators'; import { CoreService } from '../../types'; import { CoreContext } from '../core_context'; -import { InternalElasticsearchServiceSetup } from '../elasticsearch'; -import { InternalHttpServiceSetup } from '../http'; + import { Logger } from '../logging'; import { discover, PluginDiscoveryError, PluginDiscoveryErrorType } from './discovery'; import { PluginWrapper } from './plugin'; import { DiscoveredPlugin, DiscoveredPluginInternal, PluginName } from './types'; import { PluginsConfig, PluginsConfigType } from './plugins_config'; import { PluginsSystem } from './plugins_system'; -import { ContextSetup } from '../context'; +import { InternalCoreSetup } from '..'; /** @public */ export interface PluginsServiceSetup { @@ -46,11 +45,7 @@ export interface PluginsServiceStart { } /** @internal */ -export interface PluginsServiceSetupDeps { - context: ContextSetup; - elasticsearch: InternalElasticsearchServiceSetup; - http: InternalHttpServiceSetup; -} +export type PluginsServiceSetupDeps = InternalCoreSetup; /** @internal */ export interface PluginsServiceStartDeps {} // eslint-disable-line @typescript-eslint/no-empty-interface diff --git a/src/core/server/plugins/plugins_system.test.ts b/src/core/server/plugins/plugins_system.test.ts index f67bd371ff565..efd53681d6cd0 100644 --- a/src/core/server/plugins/plugins_system.test.ts +++ b/src/core/server/plugins/plugins_system.test.ts @@ -28,13 +28,13 @@ import { Env } from '../config'; import { getEnvOptions } from '../config/__mocks__/env'; import { CoreContext } from '../core_context'; import { configServiceMock } from '../config/config_service.mock'; -import { elasticsearchServiceMock } from '../elasticsearch/elasticsearch_service.mock'; -import { httpServiceMock } from '../http/http_service.mock'; import { loggingServiceMock } from '../logging/logging_service.mock'; + import { PluginWrapper } from './plugin'; import { PluginName } from './types'; import { PluginsSystem } from './plugins_system'; -import { contextServiceMock } from '../context/context_service.mock'; + +import { coreMock } from '../mocks'; const logger = loggingServiceMock.create(); function createPlugin( @@ -68,11 +68,8 @@ const configService = configServiceMock.create(); configService.atPath.mockReturnValue(new BehaviorSubject({ initialize: true })); let env: Env; let coreContext: CoreContext; -const setupDeps = { - context: contextServiceMock.createSetupContract(), - elasticsearch: elasticsearchServiceMock.createSetupContract(), - http: httpServiceMock.createSetupContract(), -}; +const setupDeps = coreMock.createInternalSetup(); + beforeEach(() => { env = Env.createDefault(getEnvOptions()); diff --git a/src/core/server/saved_objects/export/get_sorted_objects_for_export.test.ts b/src/core/server/saved_objects/export/get_sorted_objects_for_export.test.ts index 1a2a843ebb2b8..9a3449b65a941 100644 --- a/src/core/server/saved_objects/export/get_sorted_objects_for_export.test.ts +++ b/src/core/server/saved_objects/export/get_sorted_objects_for_export.test.ts @@ -18,7 +18,7 @@ */ import { getSortedObjectsForExport } from './get_sorted_objects_for_export'; -import { SavedObjectsClientMock } from '../service/saved_objects_client.mock'; +import { savedObjectsClientMock } from '../service/saved_objects_client.mock'; import { Readable } from 'stream'; import { createPromiseFromStreams, createConcatStream } from '../../../../legacy/utils/streams'; @@ -27,7 +27,7 @@ async function readStreamToCompletion(stream: Readable) { } describe('getSortedObjectsForExport()', () => { - const savedObjectsClient = SavedObjectsClientMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); afterEach(() => { savedObjectsClient.find.mockReset(); diff --git a/src/core/server/saved_objects/export/inject_nested_depdendencies.test.ts b/src/core/server/saved_objects/export/inject_nested_depdendencies.test.ts index 57feebbf67ccd..a571f62e3d1c1 100644 --- a/src/core/server/saved_objects/export/inject_nested_depdendencies.test.ts +++ b/src/core/server/saved_objects/export/inject_nested_depdendencies.test.ts @@ -18,7 +18,7 @@ */ import { SavedObject } from '../types'; -import { SavedObjectsClientMock } from '../../mocks'; +import { savedObjectsClientMock } from '../../mocks'; import { getObjectReferencesToFetch, fetchNestedDependencies } from './inject_nested_depdendencies'; describe('getObjectReferencesToFetch()', () => { @@ -109,7 +109,7 @@ describe('getObjectReferencesToFetch()', () => { }); describe('injectNestedDependencies', () => { - const savedObjectsClient = SavedObjectsClientMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); afterEach(() => { jest.resetAllMocks(); 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 df95fb75f0f4f..f0719cbf4c829 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 @@ -20,7 +20,7 @@ import { Readable } from 'stream'; import { SavedObject } from '../types'; import { importSavedObjects } from './import_saved_objects'; -import { SavedObjectsClientMock } from '../../mocks'; +import { savedObjectsClientMock } from '../../mocks'; const emptyResponse = { saved_objects: [], @@ -63,7 +63,7 @@ describe('importSavedObjects()', () => { references: [], }, ]; - const savedObjectsClient = SavedObjectsClientMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); beforeEach(() => { jest.resetAllMocks(); 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 6aab8ef5adf9e..c522d76f1ff04 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 @@ -20,7 +20,7 @@ import { Readable } from 'stream'; import { SavedObject } from '../types'; import { resolveImportErrors } from './resolve_import_errors'; -import { SavedObjectsClientMock } from '../../mocks'; +import { savedObjectsClientMock } from '../../mocks'; describe('resolveImportErrors()', () => { const savedObjects: SavedObject[] = [ @@ -63,7 +63,7 @@ describe('resolveImportErrors()', () => { ], }, ]; - const savedObjectsClient = SavedObjectsClientMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); beforeEach(() => { jest.resetAllMocks(); diff --git a/src/core/server/saved_objects/import/validate_references.test.ts b/src/core/server/saved_objects/import/validate_references.test.ts index 269cd3055b047..6642cf149eda9 100644 --- a/src/core/server/saved_objects/import/validate_references.test.ts +++ b/src/core/server/saved_objects/import/validate_references.test.ts @@ -18,10 +18,10 @@ */ import { getNonExistingReferenceAsKeys, validateReferences } from './validate_references'; -import { SavedObjectsClientMock } from '../../mocks'; +import { savedObjectsClientMock } from '../../mocks'; describe('getNonExistingReferenceAsKeys()', () => { - const savedObjectsClient = SavedObjectsClientMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); beforeEach(() => { jest.resetAllMocks(); @@ -222,7 +222,7 @@ describe('getNonExistingReferenceAsKeys()', () => { }); describe('validateReferences()', () => { - const savedObjectsClient = SavedObjectsClientMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); beforeEach(() => { jest.resetAllMocks(); 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 9e4edcd8d2943..9941bbd03f5dc 100644 --- a/src/core/server/saved_objects/service/lib/repository.test.js +++ b/src/core/server/saved_objects/service/lib/repository.test.js @@ -417,6 +417,32 @@ describe('SavedObjectsRepository', () => { }); }); + it('defaults to a refresh setting of `wait_for`', async () => { + await savedObjectsRepository.create('index-pattern', { + id: 'logstash-*', + title: 'Logstash', + }); + + expect(callAdminCluster).toHaveBeenCalledTimes(1); + expect(callAdminCluster.mock.calls[0][1]).toMatchObject({ + refresh: 'wait_for' + }); + }); + + it('accepts custom refresh settings', async () => { + await savedObjectsRepository.create('index-pattern', { + id: 'logstash-*', + title: 'Logstash', + }, { + refresh: true + }); + + expect(callAdminCluster).toHaveBeenCalledTimes(1); + expect(callAdminCluster.mock.calls[0][1]).toMatchObject({ + refresh: true + }); + }); + it('should use create action if ID defined and overwrite=false', async () => { await savedObjectsRepository.create( 'index-pattern', @@ -546,6 +572,28 @@ describe('SavedObjectsRepository', () => { ); expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); + + it('defaults to empty references array if none are provided', async () => { + await savedObjectsRepository.create( + 'index-pattern', + { + title: 'Logstash', + }, + { + id: 'logstash-*', + } + ); + + expect(callAdminCluster).toHaveBeenCalledTimes(1); + expect(callAdminCluster).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + body: expect.objectContaining({ + references: [], + }), + }) + ); + }); }); describe('#bulkCreate', () => { @@ -623,6 +671,61 @@ describe('SavedObjectsRepository', () => { expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); + it('defaults to a refresh setting of `wait_for`', async () => { + callAdminCluster.mockReturnValue({ + items: [ + { create: { type: 'config', id: 'config:one', _primary_term: 1, _seq_no: 1 } }, + ], + }); + + await savedObjectsRepository.bulkCreate([ + { + type: 'config', + id: 'one', + attributes: { title: 'Test One' }, + references: [{ name: 'ref_0', type: 'test', id: '1' }], + } + ]); + + expect(callAdminCluster).toHaveBeenCalledTimes(1); + + expect(callAdminCluster.mock.calls[0][1]).toMatchObject({ + refresh: 'wait_for' + }); + }); + + it('accepts a custom refresh setting', async () => { + callAdminCluster.mockReturnValue({ + items: [ + { create: { type: 'config', id: 'config:one', _primary_term: 1, _seq_no: 1 } }, + { create: { type: 'index-pattern', id: 'config:two', _primary_term: 1, _seq_no: 1 } }, + ], + }); + + await savedObjectsRepository.bulkCreate([ + { + type: 'config', + id: 'one', + attributes: { title: 'Test One' }, + references: [{ name: 'ref_0', type: 'test', id: '1' }], + }, + { + type: 'index-pattern', + id: 'two', + attributes: { title: 'Test Two' }, + references: [{ name: 'ref_0', type: 'test', id: '2' }], + }, + ], { + refresh: true + }); + + expect(callAdminCluster).toHaveBeenCalledTimes(1); + + expect(callAdminCluster.mock.calls[0][1]).toMatchObject({ + refresh: true + }); + }); + it('migrates the docs', async () => { callAdminCluster.mockReturnValue({ items: [ @@ -1065,6 +1168,28 @@ describe('SavedObjectsRepository', () => { expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); + + it('defaults to a refresh setting of `wait_for`', async () => { + callAdminCluster.mockReturnValue({ result: 'deleted' }); + await savedObjectsRepository.delete('globaltype', 'logstash-*'); + + expect(callAdminCluster).toHaveBeenCalledTimes(1); + expect(callAdminCluster.mock.calls[0][1]).toMatchObject({ + refresh: 'wait_for' + }); + }); + + it(`accepts a custom refresh setting`, async () => { + callAdminCluster.mockReturnValue({ result: 'deleted' }); + await savedObjectsRepository.delete('globaltype', 'logstash-*', { + refresh: false + }); + + expect(callAdminCluster).toHaveBeenCalledTimes(1); + expect(callAdminCluster.mock.calls[0][1]).toMatchObject({ + refresh: false, + }); + }); }); describe('#deleteByNamespace', () => { @@ -1104,6 +1229,26 @@ describe('SavedObjectsRepository', () => { refresh: 'wait_for', }); }); + + it('defaults to a refresh setting of `wait_for`', async () => { + callAdminCluster.mockReturnValue(deleteByQueryResults); + await savedObjectsRepository.deleteByNamespace('my-namespace'); + + expect(callAdminCluster).toHaveBeenCalledTimes(1); + expect(callAdminCluster.mock.calls[0][1]).toMatchObject({ + refresh: 'wait_for', + }); + }); + + it('accepts a custom refresh setting', async () => { + callAdminCluster.mockReturnValue(deleteByQueryResults); + await savedObjectsRepository.deleteByNamespace('my-namespace', { refresh: true }); + + expect(callAdminCluster).toHaveBeenCalledTimes(1); + expect(callAdminCluster.mock.calls[0][1]).toMatchObject({ + refresh: true, + }); + }); }); describe('#find', () => { @@ -1962,6 +2107,40 @@ describe('SavedObjectsRepository', () => { expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); + + it('defaults to a refresh setting of `wait_for`', async () => { + await savedObjectsRepository.update( + 'globaltype', + 'foo', + { + name: 'bar', + } + ); + + expect(callAdminCluster).toHaveBeenCalledTimes(1); + expect(callAdminCluster.mock.calls[0][1]).toMatchObject({ + refresh: 'wait_for' + }); + }); + + it('accepts a custom refresh setting', async () => { + await savedObjectsRepository.update( + 'globaltype', + 'foo', + { + name: 'bar', + }, + { + refresh: true, + namespace: 'foo-namespace', + } + ); + + expect(callAdminCluster).toHaveBeenCalledTimes(1); + expect(callAdminCluster.mock.calls[0][1]).toMatchObject({ + refresh: true + }); + }); }); describe('#bulkUpdate', () => { @@ -2262,6 +2441,44 @@ describe('SavedObjectsRepository', () => { }); }); + it('defaults to a refresh setting of `wait_for`', async () => { + const objects = [ + { + type: 'index-pattern', + id: `logstash-no-ref`, + attributes: { title: `Testing no-ref` }, + references: [] + } + ]; + + mockValidResponse(objects); + + await savedObjectsRepository.bulkUpdate(objects); + + expect(callAdminCluster).toHaveBeenCalledTimes(1); + + expect(callAdminCluster.mock.calls[0][1]).toMatchObject({ refresh: 'wait_for' }); + }); + + it('accepts a custom refresh setting', async () => { + const objects = [ + { + type: 'index-pattern', + id: `logstash-no-ref`, + attributes: { title: `Testing no-ref` }, + references: [] + } + ]; + + mockValidResponse(objects); + + await savedObjectsRepository.bulkUpdate(objects, { refresh: true }); + + expect(callAdminCluster).toHaveBeenCalledTimes(1); + + expect(callAdminCluster.mock.calls[0][1]).toMatchObject({ refresh: true }); + }); + it(`prepends namespace to the id but doesn't add namespace to body when providing namespace for namespaced type`, async () => { const objects = [ @@ -2504,6 +2721,29 @@ describe('SavedObjectsRepository', () => { }); }); + it('defaults to a refresh setting of `wait_for`', async () => { + await savedObjectsRepository.incrementCounter('config', 'doesnotexist', 'buildNum', { + namespace: 'foo-namespace' + }); + + expect(callAdminCluster).toHaveBeenCalledTimes(1); + expect(callAdminCluster.mock.calls[0][1]).toMatchObject({ + refresh: 'wait_for' + }); + }); + + it('accepts a custom refresh setting', async () => { + await savedObjectsRepository.incrementCounter('config', 'doesnotexist', 'buildNum', { + namespace: 'foo-namespace', + refresh: true + }); + + expect(callAdminCluster).toHaveBeenCalledTimes(1); + expect(callAdminCluster.mock.calls[0][1]).toMatchObject({ + refresh: true + }); + }); + it(`prepends namespace to the id but doesn't add namespace to body when providing namespace for namespaced type`, async () => { await savedObjectsRepository.incrementCounter('config', '6.0.0-alpha1', 'buildNum', { namespace: 'foo-namespace', diff --git a/src/core/server/saved_objects/service/lib/repository.ts b/src/core/server/saved_objects/service/lib/repository.ts index 898239e5736de..54b9938decb0a 100644 --- a/src/core/server/saved_objects/service/lib/repository.ts +++ b/src/core/server/saved_objects/service/lib/repository.ts @@ -40,6 +40,9 @@ import { SavedObjectsUpdateOptions, SavedObjectsUpdateResponse, SavedObjectsBulkUpdateObject, + SavedObjectsBulkUpdateOptions, + SavedObjectsDeleteOptions, + SavedObjectsDeleteByNamespaceOptions, } from '../saved_objects_client'; import { SavedObject, @@ -47,6 +50,7 @@ import { SavedObjectsBaseOptions, SavedObjectsFindOptions, SavedObjectsMigrationVersion, + MutatingOperationRefreshSetting, } from '../../types'; import { validateConvertFilterToKueryNode } from './filter_utils'; @@ -83,8 +87,12 @@ export interface SavedObjectsRepositoryOptions { export interface IncrementCounterOptions extends SavedObjectsBaseOptions { migrationVersion?: SavedObjectsMigrationVersion; + /** The Elasticsearch Refresh setting for this operation */ + refresh?: MutatingOperationRefreshSetting; } +const DEFAULT_REFRESH_SETTING = 'wait_for'; + export class SavedObjectsRepository { private _migrator: KibanaMigrator; private _index: string; @@ -146,15 +154,22 @@ export class SavedObjectsRepository { * @property {boolean} [options.overwrite=false] * @property {object} [options.migrationVersion=undefined] * @property {string} [options.namespace] - * @property {array} [options.references] - [{ name, type, id }] + * @property {array} [options.references=[]] - [{ name, type, id }] * @returns {promise} - { id, type, version, attributes } */ public async create( type: string, attributes: T, - options: SavedObjectsCreateOptions = { overwrite: false, references: [] } + options: SavedObjectsCreateOptions = {} ): Promise> { - const { id, migrationVersion, overwrite, namespace, references } = options; + const { + id, + migrationVersion, + overwrite = false, + namespace, + references = [], + refresh = DEFAULT_REFRESH_SETTING, + } = options; if (!this._allowedTypes.includes(type)) { throw SavedObjectsErrorHelpers.createUnsupportedTypeError(type); @@ -179,7 +194,7 @@ export class SavedObjectsRepository { const response = await this._writeToCluster(method, { id: raw._id, index: this.getIndexForType(type), - refresh: 'wait_for', + refresh, body: raw._source, }); @@ -210,7 +225,7 @@ export class SavedObjectsRepository { objects: Array>, options: SavedObjectsCreateOptions = {} ): Promise> { - const { namespace, overwrite = false } = options; + const { namespace, overwrite = false, refresh = DEFAULT_REFRESH_SETTING } = options; const time = this._getCurrentTime(); const bulkCreateParams: object[] = []; @@ -256,7 +271,7 @@ export class SavedObjectsRepository { }); const esResponse = await this._writeToCluster('bulk', { - refresh: 'wait_for', + refresh, body: bulkCreateParams, }); @@ -308,17 +323,17 @@ export class SavedObjectsRepository { * @property {string} [options.namespace] * @returns {promise} */ - async delete(type: string, id: string, options: SavedObjectsBaseOptions = {}): Promise<{}> { + async delete(type: string, id: string, options: SavedObjectsDeleteOptions = {}): Promise<{}> { if (!this._allowedTypes.includes(type)) { throw SavedObjectsErrorHelpers.createGenericNotFoundError(); } - const { namespace } = options; + const { namespace, refresh = DEFAULT_REFRESH_SETTING } = options; const response = await this._writeToCluster('delete', { id: this._serializer.generateRawId(namespace, type, id), index: this.getIndexForType(type), - refresh: 'wait_for', + refresh, ignore: [404], }); @@ -345,11 +360,16 @@ export class SavedObjectsRepository { * @param {string} namespace * @returns {promise} - { took, timed_out, total, deleted, batches, version_conflicts, noops, retries, failures } */ - async deleteByNamespace(namespace: string): Promise { + async deleteByNamespace( + namespace: string, + options: SavedObjectsDeleteByNamespaceOptions = {} + ): Promise { if (!namespace || typeof namespace !== 'string') { throw new TypeError(`namespace is required, and must be a string`); } + const { refresh = DEFAULT_REFRESH_SETTING } = options; + const allTypes = Object.keys(getRootPropertiesObjects(this._mappings)); const typesToDelete = allTypes.filter(type => !this._schema.isNamespaceAgnostic(type)); @@ -357,7 +377,7 @@ export class SavedObjectsRepository { const esOptions = { index: this.getIndicesForTypes(typesToDelete), ignore: [404], - refresh: 'wait_for', + refresh, body: { conflicts: 'proceed', ...getSearchDsl(this._mappings, this._schema, { @@ -626,7 +646,7 @@ export class SavedObjectsRepository { throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); } - const { version, namespace, references } = options; + const { version, namespace, references, refresh = DEFAULT_REFRESH_SETTING } = options; const time = this._getCurrentTime(); @@ -643,7 +663,7 @@ export class SavedObjectsRepository { id: this._serializer.generateRawId(namespace, type, id), index: this.getIndexForType(type), ...(version && decodeRequestVersion(version)), - refresh: 'wait_for', + refresh, ignore: [404], body: { doc, @@ -675,7 +695,7 @@ export class SavedObjectsRepository { */ async bulkUpdate( objects: Array>, - options: SavedObjectsBaseOptions = {} + options: SavedObjectsBulkUpdateOptions = {} ): Promise> { const time = this._getCurrentTime(); const bulkUpdateParams: object[] = []; @@ -729,9 +749,10 @@ export class SavedObjectsRepository { return { tag: 'Right' as 'Right', value: expectedResult }; }); + const { refresh = DEFAULT_REFRESH_SETTING } = options; const esResponse = bulkUpdateParams.length ? await this._writeToCluster('bulk', { - refresh: 'wait_for', + refresh, body: bulkUpdateParams, }) : {}; @@ -794,7 +815,7 @@ export class SavedObjectsRepository { throw SavedObjectsErrorHelpers.createUnsupportedTypeError(type); } - const { migrationVersion, namespace } = options; + const { migrationVersion, namespace, refresh = DEFAULT_REFRESH_SETTING } = options; const time = this._getCurrentTime(); @@ -811,7 +832,7 @@ export class SavedObjectsRepository { const response = await this._writeToCluster('update', { id: this._serializer.generateRawId(namespace, type, id), index: this.getIndexForType(type), - refresh: 'wait_for', + refresh, _source: true, body: { script: { diff --git a/src/core/server/saved_objects/service/saved_objects_client.mock.ts b/src/core/server/saved_objects/service/saved_objects_client.mock.ts index 63c9a0ee35ae0..c6de9fa94291c 100644 --- a/src/core/server/saved_objects/service/saved_objects_client.mock.ts +++ b/src/core/server/saved_objects/service/saved_objects_client.mock.ts @@ -33,4 +33,4 @@ const create = () => update: jest.fn(), } as unknown) as jest.Mocked); -export const SavedObjectsClientMock = { create }; +export const savedObjectsClientMock = { create }; diff --git a/src/core/server/saved_objects/service/saved_objects_client.ts b/src/core/server/saved_objects/service/saved_objects_client.ts index 4e04a08bd5212..550e8a1de0d80 100644 --- a/src/core/server/saved_objects/service/saved_objects_client.ts +++ b/src/core/server/saved_objects/service/saved_objects_client.ts @@ -24,6 +24,7 @@ import { SavedObjectReference, SavedObjectsMigrationVersion, SavedObjectsBaseOptions, + MutatingOperationRefreshSetting, SavedObjectsFindOptions, } from '../types'; import { SavedObjectsErrorHelpers } from './lib/errors'; @@ -40,6 +41,8 @@ export interface SavedObjectsCreateOptions extends SavedObjectsBaseOptions { /** {@inheritDoc SavedObjectsMigrationVersion} */ migrationVersion?: SavedObjectsMigrationVersion; references?: SavedObjectReference[]; + /** The Elasticsearch Refresh setting for this operation */ + refresh?: MutatingOperationRefreshSetting; } /** @@ -101,6 +104,35 @@ export interface SavedObjectsUpdateOptions extends SavedObjectsBaseOptions { version?: string; /** {@inheritdoc SavedObjectReference} */ references?: SavedObjectReference[]; + /** The Elasticsearch Refresh setting for this operation */ + refresh?: MutatingOperationRefreshSetting; +} + +/** + * + * @public + */ +export interface SavedObjectsBulkUpdateOptions extends SavedObjectsBaseOptions { + /** The Elasticsearch Refresh setting for this operation */ + refresh?: MutatingOperationRefreshSetting; +} + +/** + * + * @public + */ +export interface SavedObjectsDeleteOptions extends SavedObjectsBaseOptions { + /** The Elasticsearch Refresh setting for this operation */ + refresh?: MutatingOperationRefreshSetting; +} + +/** + * + * @public + */ +export interface SavedObjectsDeleteByNamespaceOptions extends SavedObjectsBaseOptions { + /** The Elasticsearch Refresh setting for this operation */ + refresh?: MutatingOperationRefreshSetting; } /** @@ -189,7 +221,7 @@ export class SavedObjectsClient { * @param id * @param options */ - async delete(type: string, id: string, options: SavedObjectsBaseOptions = {}) { + async delete(type: string, id: string, options: SavedObjectsDeleteOptions = {}) { return await this._repository.delete(type, id, options); } @@ -260,7 +292,7 @@ export class SavedObjectsClient { */ async bulkUpdate( objects: Array>, - options?: SavedObjectsBaseOptions + options?: SavedObjectsBulkUpdateOptions ): Promise> { return await this._repository.bulkUpdate(objects, options); } diff --git a/src/core/server/saved_objects/types.ts b/src/core/server/saved_objects/types.ts index a968b6d9392f8..2c6f5e4a520a7 100644 --- a/src/core/server/saved_objects/types.ts +++ b/src/core/server/saved_objects/types.ts @@ -142,6 +142,12 @@ export interface SavedObjectsBaseOptions { namespace?: string; } +/** + * Elasticsearch Refresh setting for mutating operation + * @public + */ +export type MutatingOperationRefreshSetting = boolean | 'wait_for'; + /** * Saved Objects is Kibana's data persisentence mechanism allowing plugins to * use Elasticsearch for storing plugin state. diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 9740f1f7032d1..14943fc96f268 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -511,6 +511,8 @@ export interface CoreSetup { elasticsearch: ElasticsearchServiceSetup; // (undocumented) http: HttpServiceSetup; + // (undocumented) + uiSettings: UiSettingsServiceSetup; } // @public @@ -737,7 +739,7 @@ export interface InternalCoreStart { // @internal (undocumented) export interface InternalUiSettingsServiceSetup { asScopedToClient(savedObjectsClient: SavedObjectsClientContract): IUiSettingsClient; - setDefaults(values: Record): void; + register(settings: Record): void; } // @public @@ -763,11 +765,8 @@ export type IScopedClusterClient = Pick(key: string) => Promise; getAll: () => Promise>; - getDefaults: () => Record; - getUserProvided: () => Promise>; + getRegistered: () => Readonly>; + getUserProvided: () => Promise>>; isOverridden: (key: string) => boolean; remove: (key: string) => Promise; removeMany: (keys: string[]) => Promise; @@ -942,6 +941,9 @@ export type MIGRATION_ASSISTANCE_INDEX_ACTION = 'upgrade' | 'reindex'; // @public (undocumented) export type MIGRATION_DEPRECATION_LEVEL = 'none' | 'info' | 'warning' | 'critical'; +// @public +export type MutatingOperationRefreshSetting = boolean | 'wait_for'; + // Warning: (ae-forgotten-export) The symbol "OnPostAuthResult" needs to be exported by the entry point index.d.ts // // @public @@ -1071,6 +1073,9 @@ export interface RequestHandlerContext { dataClient: IScopedClusterClient; adminClient: IScopedClusterClient; }; + uiSettings: { + client: IUiSettingsClient; + }; }; } @@ -1196,6 +1201,11 @@ export interface SavedObjectsBulkUpdateObject { // (undocumented) @@ -1208,9 +1218,9 @@ export class SavedObjectsClient { constructor(repository: SavedObjectsRepository); bulkCreate(objects: Array>, options?: SavedObjectsCreateOptions): Promise>; bulkGet(objects?: SavedObjectsBulkGetObject[], options?: SavedObjectsBaseOptions): Promise>; - bulkUpdate(objects: Array>, options?: SavedObjectsBaseOptions): Promise>; + bulkUpdate(objects: Array>, options?: SavedObjectsBulkUpdateOptions): Promise>; create(type: string, attributes: T, options?: SavedObjectsCreateOptions): Promise>; - delete(type: string, id: string, options?: SavedObjectsBaseOptions): Promise<{}>; + delete(type: string, id: string, options?: SavedObjectsDeleteOptions): Promise<{}>; // (undocumented) errors: typeof SavedObjectsErrorHelpers; // (undocumented) @@ -1247,6 +1257,12 @@ export interface SavedObjectsCreateOptions extends SavedObjectsBaseOptions { overwrite?: boolean; // (undocumented) references?: SavedObjectReference[]; + refresh?: MutatingOperationRefreshSetting; +} + +// @public (undocumented) +export interface SavedObjectsDeleteOptions extends SavedObjectsBaseOptions { + refresh?: MutatingOperationRefreshSetting; } // @public (undocumented) @@ -1554,6 +1570,7 @@ export class SavedObjectsSerializer { // @public (undocumented) export interface SavedObjectsUpdateOptions extends SavedObjectsBaseOptions { references?: SavedObjectReference[]; + refresh?: MutatingOperationRefreshSetting; version?: string; } @@ -1595,24 +1612,37 @@ export interface SessionStorageFactory { // @public export interface UiSettingsParams { - category: string[]; - description: string; - name: string; + category?: string[]; + description?: string; + name?: string; optionLabels?: Record; options?: string[]; readonly?: boolean; requiresPageReload?: boolean; type?: UiSettingsType; - value: SavedObjectAttribute; + value?: SavedObjectAttribute; +} + +// @public (undocumented) +export interface UiSettingsServiceSetup { + register(settings: Record): void; } // @public export type UiSettingsType = 'json' | 'markdown' | 'number' | 'select' | 'boolean' | 'string'; +// @public +export interface UserProvidedValues { + // (undocumented) + isOverridden?: boolean; + // (undocumented) + userValue?: T; +} + // Warnings were encountered during analysis: // // src/core/server/http/router/response.ts:316:3 - (ae-forgotten-export) The symbol "KibanaResponse" needs to be exported by the entry point index.d.ts -// src/core/server/plugins/plugins_service.ts:39:5 - (ae-forgotten-export) The symbol "DiscoveredPluginInternal" needs to be exported by the entry point index.d.ts +// src/core/server/plugins/plugins_service.ts:38:5 - (ae-forgotten-export) The symbol "DiscoveredPluginInternal" needs to be exported by the entry point index.d.ts ``` diff --git a/src/core/server/server.ts b/src/core/server/server.ts index 49136c8e09768..46974e204c7a4 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -21,7 +21,7 @@ import { take } from 'rxjs/operators'; import { Type } from '@kbn/config-schema'; import { ConfigService, Env, Config, ConfigPath } from './config'; -import { ElasticsearchService, ElasticsearchServiceSetup } from './elasticsearch'; +import { ElasticsearchService } from './elasticsearch'; import { HttpService, InternalHttpServiceSetup } from './http'; import { LegacyService } from './legacy'; import { Logger, LoggerFactory } from './logging'; @@ -39,7 +39,7 @@ import { config as uiSettingsConfig } from './ui_settings'; import { mapToObject } from '../utils/'; import { ContextService } from './context'; import { SavedObjectsServiceSetup } from './saved_objects/saved_objects_service'; -import { RequestHandlerContext } from '.'; +import { RequestHandlerContext, InternalCoreSetup } from '.'; const coreId = Symbol('core'); @@ -102,7 +102,7 @@ export class Server { http: httpSetup, }); - const coreSetup = { + const coreSetup: InternalCoreSetup = { context: contextServiceSetup, elasticsearch: elasticsearchServiceSetup, http: httpSetup, @@ -121,7 +121,7 @@ export class Server { legacy: legacySetup, }); - this.registerCoreContext({ ...coreSetup, savedObjects: savedObjectsSetup }); + this.registerCoreContext(coreSetup, savedObjectsSetup); return coreSetup; } @@ -163,27 +163,31 @@ export class Server { ); } - private registerCoreContext(coreSetup: { - http: InternalHttpServiceSetup; - elasticsearch: ElasticsearchServiceSetup; - savedObjects: SavedObjectsServiceSetup; - }) { + private registerCoreContext( + coreSetup: InternalCoreSetup, + savedObjects: SavedObjectsServiceSetup + ) { coreSetup.http.registerRouteHandlerContext( coreId, 'core', async (context, req): Promise => { const adminClient = await coreSetup.elasticsearch.adminClient$.pipe(take(1)).toPromise(); const dataClient = await coreSetup.elasticsearch.dataClient$.pipe(take(1)).toPromise(); + const savedObjectsClient = savedObjects.clientProvider.getClient(req); + return { savedObjects: { // Note: the client provider doesn't support new ES clients // emitted from adminClient$ - client: coreSetup.savedObjects.clientProvider.getClient(req), + client: savedObjectsClient, }, elasticsearch: { adminClient: adminClient.asScoped(req), dataClient: dataClient.asScoped(req), }, + uiSettings: { + client: coreSetup.uiSettings.asScopedToClient(savedObjectsClient), + }, }; } ); diff --git a/src/core/server/types.ts b/src/core/server/types.ts index 46c70a91721b5..4878fb9ccae19 100644 --- a/src/core/server/types.ts +++ b/src/core/server/types.ts @@ -20,4 +20,5 @@ /** This module is intended for consumption by public to avoid import issues with server-side code */ export { PluginOpaqueId } from './plugins/types'; export * from './saved_objects/types'; +export * from './ui_settings/types'; export { EnvironmentMode, PackageInfo } from './config/types'; 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 5f7e915365873..65b8792532acf 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 @@ -20,6 +20,8 @@ import sinon from 'sinon'; import Chance from 'chance'; +import { SavedObjectsErrorHelpers } from '../../saved_objects'; + import { loggingServiceMock } from '../../logging/logging_service.mock'; import * as getUpgradeableConfigNS from './get_upgradeable_config'; import { createOrUpgradeSavedConfig } from './create_or_upgrade_saved_config'; @@ -50,6 +52,7 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() { version, buildNum, log: logger.get(), + handleWriteErrors: false, ...options, }); @@ -173,85 +176,64 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() { }); }); - describe('onWriteError()', () => { - it('is called with error and attributes when savedObjectsClient.create rejects', async () => { - const { run, savedObjectsClient } = setup(); + describe('handleWriteErrors', () => { + describe('handleWriteErrors: false', () => { + it('throws write errors', async () => { + const { run, savedObjectsClient } = setup(); + const error = new Error('foo'); + savedObjectsClient.create.callsFake(async () => { + throw error; + }); - const error = new Error('foo'); - savedObjectsClient.create.callsFake(async () => { - throw error; - }); - - const onWriteError = sinon.stub(); - await run({ onWriteError }); - sinon.assert.calledOnce(onWriteError); - sinon.assert.calledWithExactly(onWriteError, error, { - buildNum, + await expect(run({ handleWriteErrors: false })).rejects.toThrowError(error); }); }); + describe('handleWriteErrors:true', () => { + it('returns undefined for ConflictError', async () => { + const { run, savedObjectsClient } = setup(); + const error = new Error('foo'); + savedObjectsClient.create.throws(SavedObjectsErrorHelpers.decorateConflictError(error)); - it('resolves with the return value of onWriteError()', async () => { - const { run, savedObjectsClient } = setup(); - - savedObjectsClient.create.callsFake(async () => { - throw new Error('foo'); + expect(await run({ handleWriteErrors: true })).toBe(undefined); }); - const result = await run({ onWriteError: () => 123 }); - expect(result).toBe(123); - }); - - it('rejects with the error from onWriteError() if it rejects', async () => { - const { run, savedObjectsClient } = setup(); + it('returns config attributes for NotAuthorizedError', async () => { + const { run, savedObjectsClient } = setup(); + const error = new Error('foo'); + savedObjectsClient.create.throws( + SavedObjectsErrorHelpers.decorateNotAuthorizedError(error) + ); - savedObjectsClient.create.callsFake(async () => { - throw new Error('foo'); + expect(await run({ handleWriteErrors: true })).toEqual({ + buildNum, + }); }); - try { - await run({ - onWriteError: (error: Error) => Promise.reject(new Error(`${error.message} bar`)), + it('returns config attributes for ForbiddenError', async () => { + const { run, savedObjectsClient } = setup(); + const error = new Error('foo'); + savedObjectsClient.create.throws(SavedObjectsErrorHelpers.decorateForbiddenError(error)); + + expect(await run({ handleWriteErrors: true })).toEqual({ + buildNum, }); - throw new Error('expected run() to reject'); - } catch (error) { - expect(error.message).toBe('foo bar'); - } - }); + }); - it('rejects with the error from onWriteError() if it throws sync', async () => { - const { run, savedObjectsClient } = setup(); + it('throws error for other SavedObjects exceptions', async () => { + const { run, savedObjectsClient } = setup(); + const error = new Error('foo'); + savedObjectsClient.create.throws(SavedObjectsErrorHelpers.decorateGeneralError(error)); - savedObjectsClient.create.callsFake(async () => { - throw new Error('foo'); + await expect(run({ handleWriteErrors: true })).rejects.toThrowError(error); }); - try { - await run({ - onWriteError: (error: Error) => { - throw new Error(`${error.message} bar`); - }, - }); - throw new Error('expected run() to reject'); - } catch (error) { - expect(error.message).toBe('foo bar'); - } - }); + it('throws error for all other exceptions', async () => { + const { run, savedObjectsClient } = setup(); + const error = new Error('foo'); + savedObjectsClient.create.throws(error); - it('rejects with the writeError if onWriteError() is undefined', async () => { - const { run, savedObjectsClient } = setup(); - - savedObjectsClient.create.callsFake(async () => { - throw new Error('foo'); + await expect(run({ handleWriteErrors: true })).rejects.toThrowError(error); }); - - try { - await run({ - onWriteError: undefined, - }); - throw new Error('expected run() to reject'); - } catch (error) { - expect(error.message).toBe('foo'); - } }); }); }); diff --git a/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.ts b/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.ts index 1655297adb6c9..809e15248b5b0 100644 --- a/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.ts +++ b/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.ts @@ -20,6 +20,7 @@ import { defaults } from 'lodash'; import { SavedObjectsClientContract, SavedObjectAttribute } from '../../saved_objects/types'; +import { SavedObjectsErrorHelpers } from '../../saved_objects/'; import { Logger } from '../../logging'; import { getUpgradeableConfig } from './get_upgradeable_config'; @@ -29,15 +30,13 @@ interface Options { version: string; buildNum: number; log: Logger; - onWriteError?: ( - error: Error, - attributes: Record - ) => Record | undefined; + handleWriteErrors: boolean; } + export async function createOrUpgradeSavedConfig( options: Options ): Promise | undefined> { - const { savedObjectsClient, version, buildNum, log, onWriteError } = options; + const { savedObjectsClient, version, buildNum, log, handleWriteErrors } = options; // try to find an older config we can upgrade const upgradeableConfig = await getUpgradeableConfig({ @@ -52,8 +51,17 @@ export async function createOrUpgradeSavedConfig { + it('finds saved objects with type "config"', async () => { + const savedObjectsClient = savedObjectsClientMock.create(); + savedObjectsClient.find.mockResolvedValue({ + saved_objects: [{ id: '7.5.0' }], + } as any); + + await getUpgradeableConfig({ savedObjectsClient, version: '7.5.0' }); + expect(savedObjectsClient.find.mock.calls[0][0].type).toBe('config'); + }); + + it('finds saved config with version < than Kibana version', async () => { + const savedConfig = { id: '7.4.0' }; + const savedObjectsClient = savedObjectsClientMock.create(); + savedObjectsClient.find.mockResolvedValue({ + saved_objects: [savedConfig], + } as any); + + const result = await getUpgradeableConfig({ savedObjectsClient, version: '7.5.0' }); + expect(result).toBe(savedConfig); + }); + + it('finds saved config with RC version === Kibana version', async () => { + const savedConfig = { id: '7.5.0-rc1' }; + const savedObjectsClient = savedObjectsClientMock.create(); + savedObjectsClient.find.mockResolvedValue({ + saved_objects: [savedConfig], + } as any); + + const result = await getUpgradeableConfig({ savedObjectsClient, version: '7.5.0' }); + expect(result).toBe(savedConfig); + }); + + it('does not find saved config with version === Kibana version', async () => { + const savedConfig = { id: '7.5.0' }; + const savedObjectsClient = savedObjectsClientMock.create(); + savedObjectsClient.find.mockResolvedValue({ + saved_objects: [savedConfig], + } as any); + + const result = await getUpgradeableConfig({ savedObjectsClient, version: '7.5.0' }); + expect(result).toBe(undefined); + }); + + it('does not find saved config with version > Kibana version', async () => { + const savedConfig = { id: '7.6.0' }; + const savedObjectsClient = savedObjectsClientMock.create(); + savedObjectsClient.find.mockResolvedValue({ + saved_objects: [savedConfig], + } as any); + + const result = await getUpgradeableConfig({ savedObjectsClient, version: '7.5.0' }); + expect(result).toBe(undefined); + }); + + it('handles empty config', async () => { + const savedObjectsClient = savedObjectsClientMock.create(); + savedObjectsClient.find.mockResolvedValue({ + saved_objects: [], + } as any); + + const result = await getUpgradeableConfig({ savedObjectsClient, version: '7.5.0' }); + expect(result).toBe(undefined); + }); +}); 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 83f8ce03299f6..9d52a339ccf91 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 @@ -18,28 +18,31 @@ */ import expect from '@kbn/expect'; -import { UnwrapPromise } from '@kbn/utility-types'; import { SavedObjectsClientContract } from 'src/core/server'; -import KbnServer from '../../../../../legacy/server/kbn_server'; -import { createTestServers } from '../../../../../test_utils/kbn_server'; +import { + createTestServers, + TestElasticsearchUtils, + TestKibanaUtils, + TestUtils, +} from '../../../../../test_utils/kbn_server'; import { createOrUpgradeSavedConfig } from '../create_or_upgrade_saved_config'; import { loggingServiceMock } from '../../../logging/logging_service.mock'; const logger = loggingServiceMock.create().get(); describe('createOrUpgradeSavedConfig()', () => { let savedObjectsClient: SavedObjectsClientContract; - let kbnServer: KbnServer; - let servers: ReturnType; - let esServer: UnwrapPromise>; - let kbn: UnwrapPromise>; + let servers: TestUtils; + let esServer: TestElasticsearchUtils; + let kbn: TestKibanaUtils; + + let kbnServer: TestKibanaUtils['kbnServer']; beforeAll(async function() { servers = createTestServers({ adjustTimeout: t => { jest.setTimeout(t); }, - settings: {}, }); esServer = await servers.startES(); kbn = await servers.startKibana(); @@ -90,6 +93,7 @@ describe('createOrUpgradeSavedConfig()', () => { version: '5.4.0', buildNum: 54099, log: logger, + handleWriteErrors: false, }); const config540 = await savedObjectsClient.get('config', '5.4.0'); @@ -116,6 +120,7 @@ describe('createOrUpgradeSavedConfig()', () => { version: '5.4.1', buildNum: 54199, log: logger, + handleWriteErrors: false, }); const config541 = await savedObjectsClient.get('config', '5.4.1'); @@ -142,6 +147,7 @@ describe('createOrUpgradeSavedConfig()', () => { version: '7.0.0-rc1', buildNum: 70010, log: logger, + handleWriteErrors: false, }); const config700rc1 = await savedObjectsClient.get('config', '7.0.0-rc1'); @@ -169,6 +175,7 @@ describe('createOrUpgradeSavedConfig()', () => { version: '7.0.0', buildNum: 70099, log: logger, + handleWriteErrors: false, }); const config700 = await savedObjectsClient.get('config', '7.0.0'); @@ -197,6 +204,7 @@ describe('createOrUpgradeSavedConfig()', () => { version: '6.2.3-rc1', buildNum: 62310, log: logger, + handleWriteErrors: false, }); const config623rc1 = await savedObjectsClient.get('config', '6.2.3-rc1'); diff --git a/src/core/server/ui_settings/index.ts b/src/core/server/ui_settings/index.ts index edd0bfc4f3a89..fd0a21bed4e12 100644 --- a/src/core/server/ui_settings/index.ts +++ b/src/core/server/ui_settings/index.ts @@ -17,16 +17,16 @@ * under the License. */ -export { - IUiSettingsClient, - UiSettingsClient, - UiSettingsServiceOptions, -} from './ui_settings_client'; +export { UiSettingsClient, UiSettingsServiceOptions } from './ui_settings_client'; export { config } from './ui_settings_config'; +export { UiSettingsService } from './ui_settings_service'; + export { + UiSettingsServiceSetup, + IUiSettingsClient, UiSettingsParams, - UiSettingsService, InternalUiSettingsServiceSetup, UiSettingsType, -} from './ui_settings_service'; + UserProvidedValues, +} from './types'; diff --git a/src/legacy/ui/ui_settings/routes/integration_tests/doc_exists.ts b/src/core/server/ui_settings/integration_tests/doc_exists.ts similarity index 100% rename from src/legacy/ui/ui_settings/routes/integration_tests/doc_exists.ts rename to src/core/server/ui_settings/integration_tests/doc_exists.ts diff --git a/src/legacy/ui/ui_settings/routes/integration_tests/doc_missing.ts b/src/core/server/ui_settings/integration_tests/doc_missing.ts similarity index 100% rename from src/legacy/ui/ui_settings/routes/integration_tests/doc_missing.ts rename to src/core/server/ui_settings/integration_tests/doc_missing.ts diff --git a/src/legacy/ui/ui_settings/routes/integration_tests/doc_missing_and_index_read_only.ts b/src/core/server/ui_settings/integration_tests/doc_missing_and_index_read_only.ts similarity index 100% rename from src/legacy/ui/ui_settings/routes/integration_tests/doc_missing_and_index_read_only.ts rename to src/core/server/ui_settings/integration_tests/doc_missing_and_index_read_only.ts diff --git a/src/legacy/ui/ui_settings/routes/integration_tests/index.test.ts b/src/core/server/ui_settings/integration_tests/index.test.ts similarity index 100% rename from src/legacy/ui/ui_settings/routes/integration_tests/index.test.ts rename to src/core/server/ui_settings/integration_tests/index.test.ts diff --git a/src/legacy/ui/ui_settings/routes/integration_tests/lib/assert.ts b/src/core/server/ui_settings/integration_tests/lib/assert.ts similarity index 100% rename from src/legacy/ui/ui_settings/routes/integration_tests/lib/assert.ts rename to src/core/server/ui_settings/integration_tests/lib/assert.ts diff --git a/src/legacy/ui/ui_settings/routes/integration_tests/lib/chance.ts b/src/core/server/ui_settings/integration_tests/lib/chance.ts similarity index 100% rename from src/legacy/ui/ui_settings/routes/integration_tests/lib/chance.ts rename to src/core/server/ui_settings/integration_tests/lib/chance.ts diff --git a/src/legacy/ui/ui_settings/routes/integration_tests/lib/index.ts b/src/core/server/ui_settings/integration_tests/lib/index.ts similarity index 100% rename from src/legacy/ui/ui_settings/routes/integration_tests/lib/index.ts rename to src/core/server/ui_settings/integration_tests/lib/index.ts diff --git a/src/legacy/ui/ui_settings/routes/integration_tests/lib/servers.ts b/src/core/server/ui_settings/integration_tests/lib/servers.ts similarity index 83% rename from src/legacy/ui/ui_settings/routes/integration_tests/lib/servers.ts rename to src/core/server/ui_settings/integration_tests/lib/servers.ts index ae0ef1c91411e..a1be1e7e7291e 100644 --- a/src/legacy/ui/ui_settings/routes/integration_tests/lib/servers.ts +++ b/src/core/server/ui_settings/integration_tests/lib/servers.ts @@ -17,20 +17,24 @@ * under the License. */ -import { UnwrapPromise } from '@kbn/utility-types'; import { SavedObjectsClientContract, IUiSettingsClient } from 'src/core/server'; -import KbnServer from '../../../../../server/kbn_server'; -import { createTestServers } from '../../../../../../test_utils/kbn_server'; -import { CallCluster } from '../../../../../../legacy/core_plugins/elasticsearch'; +import { + createTestServers, + TestElasticsearchUtils, + TestKibanaUtils, + TestUtils, +} from '../../../../../test_utils/kbn_server'; +import { CallCluster } from '../../../../../legacy/core_plugins/elasticsearch'; -let kbnServer: KbnServer; -let servers: ReturnType; -let esServer: UnwrapPromise>; -let kbn: UnwrapPromise>; +let servers: TestUtils; +let esServer: TestElasticsearchUtils; +let kbn: TestKibanaUtils; + +let kbnServer: TestKibanaUtils['kbnServer']; interface AllServices { - kbnServer: KbnServer; + kbnServer: TestKibanaUtils['kbnServer']; savedObjectsClient: SavedObjectsClientContract; callCluster: CallCluster; uiSettings: IUiSettingsClient; diff --git a/src/core/server/ui_settings/routes/delete.ts b/src/core/server/ui_settings/routes/delete.ts new file mode 100644 index 0000000000000..ee4c05325fcd4 --- /dev/null +++ b/src/core/server/ui_settings/routes/delete.ts @@ -0,0 +1,61 @@ +/* + * 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 } from '@kbn/config-schema'; + +import { IRouter } from '../../http'; +import { SavedObjectsErrorHelpers } from '../../saved_objects'; +import { CannotOverrideError } from '../ui_settings_errors'; + +const validate = { + params: schema.object({ + key: schema.string(), + }), +}; + +export function registerDeleteRoute(router: IRouter) { + router.delete( + { path: '/api/kibana/settings/{key}', validate }, + async (context, request, response) => { + try { + const uiSettingsClient = context.core.uiSettings.client; + + await uiSettingsClient.remove(request.params.key); + + return response.ok({ + body: { + settings: await uiSettingsClient.getUserProvided(), + }, + }); + } catch (error) { + if (SavedObjectsErrorHelpers.isSavedObjectsClientError(error)) { + return response.customError({ + body: error, + statusCode: error.output.statusCode, + }); + } + + if (error instanceof CannotOverrideError) { + return response.badRequest({ body: error }); + } + + throw error; + } + } + ); +} diff --git a/src/legacy/core_plugins/kibana/public/discover/doc/doc_directive.ts b/src/core/server/ui_settings/routes/get.ts similarity index 51% rename from src/legacy/core_plugins/kibana/public/discover/doc/doc_directive.ts rename to src/core/server/ui_settings/routes/get.ts index 3ee510f47ce5b..d249369a1ace7 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc/doc_directive.ts +++ b/src/core/server/ui_settings/routes/get.ts @@ -16,21 +16,30 @@ * specific language governing permissions and limitations * under the License. */ -import { wrapInI18nContext } from 'ui/i18n'; -// @ts-ignore -import { uiModules } from 'ui/modules'; -import { Doc } from './doc'; +import { IRouter } from '../../http'; +import { SavedObjectsErrorHelpers } from '../../saved_objects'; -uiModules.get('apps/discover').directive('discoverDoc', function(reactDirective: any) { - return reactDirective( - wrapInI18nContext(Doc), - [ - ['id', { watchDepth: 'value' }], - ['index', { watchDepth: 'value' }], - ['indexPatternId', { watchDepth: 'reference' }], - ['indexPatternService', { watchDepth: 'reference' }], - ['esClient', { watchDepth: 'reference' }], - ], - { restrict: 'E' } +export function registerGetRoute(router: IRouter) { + router.get( + { path: '/api/kibana/settings', validate: false }, + async (context, request, response) => { + try { + const uiSettingsClient = context.core.uiSettings.client; + return response.ok({ + body: { + settings: await uiSettingsClient.getUserProvided(), + }, + }); + } catch (error) { + if (SavedObjectsErrorHelpers.isSavedObjectsClientError(error)) { + return response.customError({ + body: error, + statusCode: error.output.statusCode, + }); + } + + throw error; + } + } ); -}); +} diff --git a/src/core/server/ui_settings/routes/index.ts b/src/core/server/ui_settings/routes/index.ts new file mode 100644 index 0000000000000..d70d55d725938 --- /dev/null +++ b/src/core/server/ui_settings/routes/index.ts @@ -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 { IRouter } from 'src/core/server'; + +import { registerDeleteRoute } from './delete'; +import { registerGetRoute } from './get'; +import { registerSetManyRoute } from './set_many'; +import { registerSetRoute } from './set'; + +export function registerRoutes(router: IRouter) { + registerGetRoute(router); + registerDeleteRoute(router); + registerSetRoute(router); + registerSetManyRoute(router); +} diff --git a/src/core/server/ui_settings/routes/set.ts b/src/core/server/ui_settings/routes/set.ts new file mode 100644 index 0000000000000..51ad256b51335 --- /dev/null +++ b/src/core/server/ui_settings/routes/set.ts @@ -0,0 +1,67 @@ +/* + * 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 } from '@kbn/config-schema'; + +import { IRouter } from '../../http'; +import { SavedObjectsErrorHelpers } from '../../saved_objects'; +import { CannotOverrideError } from '../ui_settings_errors'; + +const validate = { + params: schema.object({ + key: schema.string(), + }), + body: schema.object({ + value: schema.any(), + }), +}; + +export function registerSetRoute(router: IRouter) { + router.post( + { path: '/api/kibana/settings/{key}', validate }, + async (context, request, response) => { + try { + const uiSettingsClient = context.core.uiSettings.client; + + const { key } = request.params; + const { value } = request.body; + + await uiSettingsClient.set(key, value); + + return response.ok({ + body: { + settings: await uiSettingsClient.getUserProvided(), + }, + }); + } catch (error) { + if (SavedObjectsErrorHelpers.isSavedObjectsClientError(error)) { + return response.customError({ + body: error, + statusCode: error.output.statusCode, + }); + } + + if (error instanceof CannotOverrideError) { + return response.badRequest({ body: error }); + } + + throw error; + } + } + ); +} diff --git a/src/core/server/ui_settings/routes/set_many.ts b/src/core/server/ui_settings/routes/set_many.ts new file mode 100644 index 0000000000000..3794eba004bee --- /dev/null +++ b/src/core/server/ui_settings/routes/set_many.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 { schema } from '@kbn/config-schema'; + +import { IRouter } from '../../http'; +import { SavedObjectsErrorHelpers } from '../../saved_objects'; +import { CannotOverrideError } from '../ui_settings_errors'; + +const validate = { + body: schema.object({ + changes: schema.object({}, { allowUnknowns: true }), + }), +}; + +export function registerSetManyRoute(router: IRouter) { + router.post({ path: '/api/kibana/settings', validate }, async (context, request, response) => { + try { + const uiSettingsClient = context.core.uiSettings.client; + + const { changes } = request.body; + + await uiSettingsClient.setMany(changes); + + return response.ok({ + body: { + settings: await uiSettingsClient.getUserProvided(), + }, + }); + } catch (error) { + if (SavedObjectsErrorHelpers.isSavedObjectsClientError(error)) { + return response.customError({ + body: error, + statusCode: error.output.statusCode, + }); + } + + if (error instanceof CannotOverrideError) { + return response.badRequest({ body: error }); + } + + throw error; + } + }); +} diff --git a/src/core/server/ui_settings/types.ts b/src/core/server/ui_settings/types.ts new file mode 100644 index 0000000000000..0fa6b3702af24 --- /dev/null +++ b/src/core/server/ui_settings/types.ts @@ -0,0 +1,141 @@ +/* + * 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 { SavedObjectsClientContract, SavedObjectAttribute } from '../saved_objects/types'; +/** + * Server-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. + * + * @public + */ +export interface IUiSettingsClient { + /** + * Returns registered uiSettings values {@link UiSettingsParams} + */ + getRegistered: () => Readonly>; + /** + * Retrieves uiSettings values set by the user with fallbacks to default values if not specified. + */ + get: (key: string) => Promise; + /** + * Retrieves a set of all uiSettings values set by the user with fallbacks to default values if not specified. + */ + getAll: () => Promise>; + /** + * Retrieves a set of all uiSettings values set by the user. + */ + getUserProvided: () => Promise< + Record> + >; + /** + * Writes multiple uiSettings values and marks them as set by the user. + */ + setMany: (changes: Record) => Promise; + /** + * Writes uiSettings value and marks it as set by the user. + */ + set: (key: string, value: T) => Promise; + /** + * Removes uiSettings value by key. + */ + remove: (key: string) => Promise; + /** + * Removes multiple uiSettings values by keys. + */ + removeMany: (keys: string[]) => Promise; + /** + * Shows whether the uiSettings value set by the user. + */ + isOverridden: (key: string) => boolean; +} + +/** + * Describes the values explicitly set by user. + * @public + * */ +export interface UserProvidedValues { + userValue?: T; + isOverridden?: boolean; +} + +/** + * UI element type to represent the settings. + * @public + * */ +export type UiSettingsType = 'json' | 'markdown' | 'number' | 'select' | 'boolean' | 'string'; + +/** + * UiSettings parameters defined by the plugins. + * @public + * */ +export interface UiSettingsParams { + /** title in the UI */ + name?: string; + /** default value to fall back to if a user doesn't provide any */ + value?: SavedObjectAttribute; + /** description provided to a user in UI */ + description?: string; + /** used to group the configured setting in the UI */ + category?: string[]; + /** array of permitted values for this setting */ + options?: string[]; + /** text labels for 'select' type UI element */ + optionLabels?: Record; + /** a flag indicating whether new value applying requires page reloading */ + requiresPageReload?: boolean; + /** a flag indicating that value cannot be changed */ + readonly?: boolean; + /** defines a type of UI element {@link UiSettingsType} */ + type?: UiSettingsType; +} + +/** @internal */ +export interface InternalUiSettingsServiceSetup { + /** + * Sets settings with default values for the uiSettings. + * @param settings + */ + register(settings: Record): void; + /** + * Creates uiSettings client with provided *scoped* saved objects client {@link IUiSettingsClient} + * @param savedObjectsClient + */ + asScopedToClient(savedObjectsClient: SavedObjectsClientContract): IUiSettingsClient; +} + +/** @public */ +export interface UiSettingsServiceSetup { + /** + * Sets settings with default values for the uiSettings. + * @param settings + * + * @example + * setup(core: CoreSetup){ + * core.uiSettings.register([{ + * foo: { + * name: i18n.translate('my foo settings'), + * value: true, + * description: 'add some awesomeness', + * }, + * }]); + * } + */ + register(settings: Record): void; +} diff --git a/src/core/server/ui_settings/ui_settings_client.test.ts b/src/core/server/ui_settings/ui_settings_client.test.ts index 59c13fbebee70..1c99637a89fed 100644 --- a/src/core/server/ui_settings/ui_settings_client.test.ts +++ b/src/core/server/ui_settings/ui_settings_client.test.ts @@ -24,6 +24,7 @@ import sinon from 'sinon'; import { loggingServiceMock } from '../logging/logging_service.mock'; import { UiSettingsClient } from './ui_settings_client'; +import { CannotOverrideError } from './ui_settings_errors'; import * as createOrUpgradeSavedConfigNS from './create_or_upgrade_saved_config/create_or_upgrade_saved_config'; import { createObjectsClientStub, savedObjectsClientErrors } from './create_objects_client_stub'; @@ -119,7 +120,12 @@ describe('ui settings', () => { await uiSettings.setMany({ foo: 'bar' }); sinon.assert.calledTwice(savedObjectsClient.update); + sinon.assert.calledOnce(createOrUpgradeSavedConfig); + sinon.assert.calledWith( + createOrUpgradeSavedConfig, + sinon.match({ handleWriteErrors: false }) + ); }); it('only tried to auto create once and throws NotFound', async () => { @@ -135,9 +141,14 @@ describe('ui settings', () => { sinon.assert.calledTwice(savedObjectsClient.update); sinon.assert.calledOnce(createOrUpgradeSavedConfig); + + sinon.assert.calledWith( + createOrUpgradeSavedConfig, + sinon.match({ handleWriteErrors: false }) + ); }); - it('throws an error if any key is overridden', async () => { + it('throws CannotOverrideError if the key is overridden', async () => { const { uiSettings } = setup({ overrides: { foo: 'bar', @@ -150,6 +161,7 @@ describe('ui settings', () => { foo: 'baz', }); } catch (error) { + expect(error).to.be.a(CannotOverrideError); expect(error.message).to.be('Unable to update "foo" because it is overridden'); } }); @@ -167,7 +179,7 @@ describe('ui settings', () => { assertUpdateQuery({ one: 'value' }); }); - it('throws an error if the key is overridden', async () => { + it('throws CannotOverrideError if the key is overridden', async () => { const { uiSettings } = setup({ overrides: { foo: 'bar', @@ -177,6 +189,7 @@ describe('ui settings', () => { try { await uiSettings.set('foo', 'baz'); } catch (error) { + expect(error).to.be.a(CannotOverrideError); expect(error.message).to.be('Unable to update "foo" because it is overridden'); } }); @@ -194,7 +207,7 @@ describe('ui settings', () => { assertUpdateQuery({ one: null }); }); - it('throws an error if the key is overridden', async () => { + it('throws CannotOverrideError if the key is overridden', async () => { const { uiSettings } = setup({ overrides: { foo: 'bar', @@ -204,6 +217,7 @@ describe('ui settings', () => { try { await uiSettings.remove('foo'); } catch (error) { + expect(error).to.be.a(CannotOverrideError); expect(error.message).to.be('Unable to update "foo" because it is overridden'); } }); @@ -227,7 +241,7 @@ describe('ui settings', () => { assertUpdateQuery({ one: null, two: null, three: null }); }); - it('throws an error if any key is overridden', async () => { + it('throws CannotOverrideError if any key is overridden', async () => { const { uiSettings } = setup({ overrides: { foo: 'bar', @@ -237,18 +251,18 @@ describe('ui settings', () => { try { await uiSettings.setMany({ baz: 'baz', foo: 'foo' }); } catch (error) { + expect(error).to.be.a(CannotOverrideError); expect(error.message).to.be('Unable to update "foo" because it is overridden'); } }); }); - describe('#getDefaults()', () => { - it('returns the defaults passed to the constructor', () => { + describe('#getRegistered()', () => { + it('returns the registered settings passed to the constructor', () => { const value = chance.word(); - const { uiSettings } = setup({ defaults: { key: { value } } }); - expect(uiSettings.getDefaults()).to.eql({ - key: { value }, - }); + const defaults = { key: { value } }; + const { uiSettings } = setup({ defaults }); + expect(uiSettings.getRegistered()).to.be(defaults); }); }); @@ -284,31 +298,48 @@ describe('ui settings', () => { }); }); - it.skip('returns an empty object on NotFound responses', async () => { - const { uiSettings, savedObjectsClient } = setup(); + it('automatically creates the savedConfig if it is missing and returns empty object', async () => { + const { uiSettings, savedObjectsClient, createOrUpgradeSavedConfig } = setup(); + savedObjectsClient.get + .onFirstCall() + .throws(savedObjectsClientErrors.createGenericNotFoundError()) + .onSecondCall() + .returns({ attributes: {} }); - const error = savedObjectsClientErrors.createGenericNotFoundError(); - savedObjectsClient.get.throws(error); + expect(await uiSettings.getUserProvided()).to.eql({}); + + sinon.assert.calledTwice(savedObjectsClient.get); + + sinon.assert.calledOnce(createOrUpgradeSavedConfig); + sinon.assert.calledWith(createOrUpgradeSavedConfig, sinon.match({ handleWriteErrors: true })); + }); - expect(await uiSettings.getUserProvided({})).to.eql({}); + it('returns result of savedConfig creation in case of notFound error', async () => { + const { uiSettings, savedObjectsClient, createOrUpgradeSavedConfig } = setup(); + createOrUpgradeSavedConfig.resolves({ foo: 'bar ' }); + savedObjectsClient.get.throws(savedObjectsClientErrors.createGenericNotFoundError()); + + expect(await uiSettings.getUserProvided()).to.eql({ foo: { userValue: 'bar ' } }); }); it('returns an empty object on Forbidden responses', async () => { - const { uiSettings, savedObjectsClient } = setup(); + const { uiSettings, savedObjectsClient, createOrUpgradeSavedConfig } = setup(); const error = savedObjectsClientErrors.decorateForbiddenError(new Error()); savedObjectsClient.get.throws(error); expect(await uiSettings.getUserProvided()).to.eql({}); + sinon.assert.notCalled(createOrUpgradeSavedConfig); }); it('returns an empty object on EsUnavailable responses', async () => { - const { uiSettings, savedObjectsClient } = setup(); + const { uiSettings, savedObjectsClient, createOrUpgradeSavedConfig } = setup(); const error = savedObjectsClientErrors.decorateEsUnavailableError(new Error()); savedObjectsClient.get.throws(error); expect(await uiSettings.getUserProvided()).to.eql({}); + sinon.assert.notCalled(createOrUpgradeSavedConfig); }); it('throws Unauthorized errors', async () => { @@ -346,6 +377,7 @@ describe('ui settings', () => { const overrides = { foo: 'bar', + baz: null, }; const { uiSettings } = setup({ esDocSource, overrides }); @@ -357,57 +389,7 @@ describe('ui settings', () => { userValue: 'bar', isOverridden: true, }, - }); - }); - }); - - describe('#getRaw()', () => { - it('pulls user configuration from ES', async () => { - const esDocSource = {}; - const { uiSettings, assertGetQuery } = setup({ esDocSource }); - await uiSettings.getRaw(); - assertGetQuery(); - }); - - it(`without user configuration it's equal to the defaults`, async () => { - const esDocSource = {}; - const defaults = { key: { value: chance.word() } }; - const { uiSettings } = setup({ esDocSource, defaults }); - const result = await uiSettings.getRaw(); - expect(result).to.eql(defaults); - }); - - it(`user configuration gets merged with defaults`, async () => { - const esDocSource = { foo: 'bar' }; - const defaults = { key: { value: chance.word() } }; - const { uiSettings } = setup({ esDocSource, defaults }); - const result = await uiSettings.getRaw(); - - expect(result).to.eql({ - foo: { - userValue: 'bar', - }, - key: { - value: defaults.key.value, - }, - }); - }); - - it('includes the values for overridden keys', async () => { - const esDocSource = { foo: 'bar' }; - const defaults = { key: { value: chance.word() } }; - const overrides = { foo: true }; - const { uiSettings } = setup({ esDocSource, defaults, overrides }); - const result = await uiSettings.getRaw(); - - expect(result).to.eql({ - foo: { - userValue: true, - isOverridden: true, - }, - key: { - value: defaults.key.value, - }, + baz: { isOverridden: true }, }); }); }); @@ -545,22 +527,4 @@ describe('ui settings', () => { expect(uiSettings.isOverridden('bar')).to.be(true); }); }); - - describe('#assertUpdateAllowed()', () => { - it('returns false if no overrides defined', () => { - const { uiSettings } = setup(); - expect(uiSettings.assertUpdateAllowed('foo')).to.be(undefined); - }); - it('throws 400 Boom error when keys is overridden', () => { - const { uiSettings } = setup({ overrides: { foo: true } }); - expect(() => uiSettings.assertUpdateAllowed('foo')).to.throwError(error => { - expect(error).to.have.property( - 'message', - 'Unable to update "foo" because it is overridden' - ); - expect(error).to.have.property('isBoom', true); - expect(error.output).to.have.property('statusCode', 400); - }); - }); - }); }); diff --git a/src/core/server/ui_settings/ui_settings_client.ts b/src/core/server/ui_settings/ui_settings_client.ts index c495d1b4c4567..423ff2a1dfd90 100644 --- a/src/core/server/ui_settings/ui_settings_client.ts +++ b/src/core/server/ui_settings/ui_settings_client.ts @@ -17,12 +17,13 @@ * under the License. */ import { defaultsDeep } from 'lodash'; -import Boom from 'boom'; +import { SavedObjectsErrorHelpers } from '../saved_objects'; import { SavedObjectsClientContract, SavedObjectAttribute } from '../saved_objects/types'; import { Logger } from '../logging'; import { createOrUpgradeSavedConfig } from './create_or_upgrade_saved_config'; -import { UiSettingsParams } from './ui_settings_service'; +import { IUiSettingsClient, UiSettingsParams } from './types'; +import { CannotOverrideError } from './ui_settings_errors'; export interface UiSettingsServiceOptions { type: string; @@ -49,52 +50,6 @@ type UiSettingsRawValue = UiSettingsParams & UserProvidedValue; type UserProvided = Record>; type UiSettingsRaw = Record; -/** - * Service that provides access to the UiSettings stored in elasticsearch. - * - * @public - */ -export interface IUiSettingsClient { - /** - * Returns uiSettings default values {@link UiSettingsParams} - */ - getDefaults: () => Record; - /** - * Retrieves uiSettings values set by the user with fallbacks to default values if not specified. - */ - get: (key: string) => Promise; - /** - * Retrieves a set of all uiSettings values set by the user with fallbacks to default values if not specified. - */ - getAll: () => Promise>; - /** - * Retrieves a set of all uiSettings values set by the user. - */ - getUserProvided: () => Promise< - Record - >; - /** - * Writes multiple uiSettings values and marks them as set by the user. - */ - setMany: (changes: Record) => Promise; - /** - * Writes uiSettings value and marks it as set by the user. - */ - set: (key: string, value: T) => Promise; - /** - * Removes uiSettings value by key. - */ - remove: (key: string) => Promise; - /** - * Removes multiple uiSettings values by keys. - */ - removeMany: (keys: string[]) => Promise; - /** - * Shows whether the uiSettings value set by the user. - */ - isOverridden: (key: string) => boolean; -} - export class UiSettingsClient implements IUiSettingsClient { private readonly type: UiSettingsServiceOptions['type']; private readonly id: UiSettingsServiceOptions['id']; @@ -116,7 +71,7 @@ export class UiSettingsClient implements IUiSettingsClient { this.log = log; } - getDefaults() { + getRegistered() { return this.defaults; } @@ -138,19 +93,11 @@ export class UiSettingsClient implements IUiSettingsClient { ); } - // NOTE: should be a private method - async getRaw(): Promise { - const userProvided = await this.getUserProvided(); - return defaultsDeep(userProvided, this.defaults); - } - - async getUserProvided( - options: ReadOptions = {} - ): Promise> { + async getUserProvided(): Promise> { const userProvided: UserProvided = {}; // write the userValue for each key stored in the saved object that is not overridden - for (const [key, userValue] of Object.entries(await this.read(options))) { + for (const [key, userValue] of Object.entries(await this.read())) { if (userValue !== null && !this.isOverridden(key)) { userProvided[key] = { userValue, @@ -192,13 +139,17 @@ export class UiSettingsClient implements IUiSettingsClient { return this.overrides.hasOwnProperty(key); } - // NOTE: should be private method - assertUpdateAllowed(key: string) { + private assertUpdateAllowed(key: string) { if (this.isOverridden(key)) { - throw Boom.badRequest(`Unable to update "${key}" because it is overridden`); + throw new CannotOverrideError(`Unable to update "${key}" because it is overridden`); } } + private async getRaw(): Promise { + const userProvided = await this.getUserProvided(); + return defaultsDeep(userProvided, this.defaults); + } + private async write({ changes, autoCreateOrUpgradeIfMissing = true, @@ -213,8 +164,7 @@ export class UiSettingsClient implements IUiSettingsClient { try { await this.savedObjectsClient.update(this.type, this.id, changes); } catch (error) { - const { isNotFoundError } = this.savedObjectsClient.errors; - if (!isNotFoundError(error) || !autoCreateOrUpgradeIfMissing) { + if (!SavedObjectsErrorHelpers.isNotFoundError(error) || !autoCreateOrUpgradeIfMissing) { throw error; } @@ -223,6 +173,7 @@ export class UiSettingsClient implements IUiSettingsClient { version: this.id, buildNum: this.buildNum, log: this.log, + handleWriteErrors: false, }); await this.write({ @@ -236,37 +187,17 @@ export class UiSettingsClient implements IUiSettingsClient { ignore401Errors = false, autoCreateOrUpgradeIfMissing = true, }: ReadOptions = {}): Promise> { - const { - isConflictError, - isNotFoundError, - isForbiddenError, - isNotAuthorizedError, - } = this.savedObjectsClient.errors; - try { const resp = await this.savedObjectsClient.get(this.type, this.id); return resp.attributes; } catch (error) { - if (isNotFoundError(error) && autoCreateOrUpgradeIfMissing) { + if (SavedObjectsErrorHelpers.isNotFoundError(error) && autoCreateOrUpgradeIfMissing) { const failedUpgradeAttributes = await createOrUpgradeSavedConfig({ savedObjectsClient: this.savedObjectsClient, version: this.id, buildNum: this.buildNum, log: this.log, - onWriteError(writeError, attributes) { - if (isConflictError(writeError)) { - // trigger `!failedUpgradeAttributes` check below, since another - // request caused the uiSettings object to be created so we can - // just re-read - return; - } - - if (isNotAuthorizedError(writeError) || isForbiddenError(writeError)) { - return attributes; - } - - throw writeError; - }, + handleWriteErrors: true, }); if (!failedUpgradeAttributes) { diff --git a/src/core/server/ui_settings/ui_settings_errors.ts b/src/core/server/ui_settings/ui_settings_errors.ts new file mode 100644 index 0000000000000..d8fc32b111e44 --- /dev/null +++ b/src/core/server/ui_settings/ui_settings_errors.ts @@ -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. + */ + +export class CannotOverrideError extends Error { + public cause?: Error; + + constructor(message: string, cause?: Error) { + super(message); + this.cause = cause; + + // 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, CannotOverrideError.prototype); + } +} 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 2127faf0d2029..bb21109a2f967 100644 --- a/src/core/server/ui_settings/ui_settings_service.mock.ts +++ b/src/core/server/ui_settings/ui_settings_service.mock.ts @@ -17,12 +17,11 @@ * under the License. */ -import { IUiSettingsClient } from './ui_settings_client'; -import { InternalUiSettingsServiceSetup } from './ui_settings_service'; +import { IUiSettingsClient, InternalUiSettingsServiceSetup } from './types'; const createClientMock = () => { const mocked: jest.Mocked = { - getDefaults: jest.fn(), + getRegistered: jest.fn(), get: jest.fn(), getAll: jest.fn(), getUserProvided: jest.fn(), @@ -38,7 +37,7 @@ const createClientMock = () => { const createSetupMock = () => { const mocked: jest.Mocked = { - setDefaults: jest.fn(), + register: jest.fn(), asScopedToClient: jest.fn(), }; @@ -48,6 +47,6 @@ const createSetupMock = () => { }; export const uiSettingsServiceMock = { - createSetup: createSetupMock, + createSetupContract: createSetupMock, createClient: createClientMock, }; 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 832d61bdb4137..d7a085a220190 100644 --- a/src/core/server/ui_settings/ui_settings_service.test.ts +++ b/src/core/server/ui_settings/ui_settings_service.test.ts @@ -23,7 +23,7 @@ import { MockUiSettingsClientConstructor } from './ui_settings_service.test.mock import { UiSettingsService } from './ui_settings_service'; import { httpServiceMock } from '../http/http_service.mock'; import { loggingServiceMock } from '../logging/logging_service.mock'; -import { SavedObjectsClientMock } from '../mocks'; +import { savedObjectsClientMock } from '../mocks'; import { mockCoreContext } from '../core_context.mock'; const overrides = { @@ -43,7 +43,7 @@ const coreContext = mockCoreContext.create(); coreContext.configService.atPath.mockReturnValue(new BehaviorSubject({ overrides })); const httpSetup = httpServiceMock.createSetupContract(); const setupDeps = { http: httpSetup }; -const savedObjectsClient = SavedObjectsClientMock.create(); +const savedObjectsClient = savedObjectsClientMock.create(); afterEach(() => { MockUiSettingsClientConstructor.mockClear(); @@ -52,6 +52,14 @@ afterEach(() => { describe('uiSettings', () => { describe('#setup', () => { describe('#asScopedToClient', () => { + it('passes saved object type "config" to UiSettingsClient', async () => { + const service = new UiSettingsService(coreContext); + 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 service = new UiSettingsService(coreContext); const setup = await service.setup(setupDeps); @@ -86,7 +94,7 @@ describe('uiSettings', () => { const service = new UiSettingsService(coreContext); const setup = await service.setup(setupDeps); - setup.setDefaults(defaults); + setup.register(defaults); setup.asScopedToClient(savedObjectsClient); expect(MockUiSettingsClientConstructor).toBeCalledTimes(1); @@ -95,13 +103,13 @@ describe('uiSettings', () => { }); }); - describe('#setDefaults', () => { - it('throws if set defaults for the same key twice', async () => { + describe('#register', () => { + it('throws if registers the same key twice', async () => { const service = new UiSettingsService(coreContext); const setup = await service.setup(setupDeps); - setup.setDefaults(defaults); - expect(() => setup.setDefaults(defaults)).toThrowErrorMatchingInlineSnapshot( - `"uiSettings defaults for key [foo] has been already set"` + setup.register(defaults); + expect(() => setup.register(defaults)).toThrowErrorMatchingInlineSnapshot( + `"uiSettings for the key [foo] has been already registered"` ); }); }); diff --git a/src/core/server/ui_settings/ui_settings_service.ts b/src/core/server/ui_settings/ui_settings_service.ts index 746fa514c5d4b..a8f5663f8bd1e 100644 --- a/src/core/server/ui_settings/ui_settings_service.ts +++ b/src/core/server/ui_settings/ui_settings_service.ts @@ -26,58 +26,16 @@ import { Logger } from '../logging'; import { SavedObjectsClientContract, SavedObjectAttribute } from '../saved_objects/types'; import { InternalHttpServiceSetup } from '../http'; import { UiSettingsConfigType } from './ui_settings_config'; -import { IUiSettingsClient, UiSettingsClient } from './ui_settings_client'; +import { UiSettingsClient } from './ui_settings_client'; +import { InternalUiSettingsServiceSetup, UiSettingsParams } from './types'; import { mapToObject } from '../../utils/'; +import { registerRoutes } from './routes'; + interface SetupDeps { http: InternalHttpServiceSetup; } -/** - * UI element type to represent the settings. - * @public - * */ -export type UiSettingsType = 'json' | 'markdown' | 'number' | 'select' | 'boolean' | 'string'; - -/** - * UiSettings parameters defined by the plugins. - * @public - * */ -export interface UiSettingsParams { - /** title in the UI */ - name: string; - /** default value to fall back to if a user doesn't provide any */ - value: SavedObjectAttribute; - /** description provided to a user in UI */ - description: string; - /** used to group the configured setting in the UI */ - category: string[]; - /** a range of valid values */ - options?: string[]; - /** text labels for 'select' type UI element */ - optionLabels?: Record; - /** a flag indicating whether new value applying requires page reloading */ - requiresPageReload?: boolean; - /** a flag indicating that value cannot be changed */ - readonly?: boolean; - /** defines a type of UI element {@link UiSettingsType} */ - type?: UiSettingsType; -} - -/** @internal */ -export interface InternalUiSettingsServiceSetup { - /** - * Sets the parameters with default values for the uiSettings. - * @param values - */ - setDefaults(values: Record): void; - /** - * Creates uiSettings client with provided *scoped* saved objects client {@link IUiSettingsClient} - * @param values - */ - asScopedToClient(savedObjectsClient: SavedObjectsClientContract): IUiSettingsClient; -} - /** @internal */ export class UiSettingsService implements CoreService { private readonly log: Logger; @@ -90,12 +48,13 @@ export class UiSettingsService implements CoreService { + registerRoutes(deps.http.createRouter('')); this.log.debug('Setting up ui settings service'); const overrides = await this.getOverrides(deps); const { version, buildNum } = this.coreContext.env.packageInfo; return { - setDefaults: this.setDefaults.bind(this), + register: this.register.bind(this), asScopedToClient: (savedObjectsClient: SavedObjectsClientContract) => { return new UiSettingsClient({ type: 'config', @@ -114,10 +73,10 @@ export class UiSettingsService implements CoreService = {}) { - Object.entries(values).forEach(([key, value]) => { + private register(settings: Record = {}) { + Object.entries(settings).forEach(([key, value]) => { if (this.uiSettingsDefaults.has(key)) { - throw new Error(`uiSettings defaults for key [${key}] has been already set`); + throw new Error(`uiSettings for the key [${key}] has been already registered`); } this.uiSettingsDefaults.set(key, value); }); diff --git a/src/core/utils/integration_tests/deep_freeze.test.ts b/src/core/utils/integration_tests/deep_freeze.test.ts index a9440f70ae470..e6625542bc38a 100644 --- a/src/core/utils/integration_tests/deep_freeze.test.ts +++ b/src/core/utils/integration_tests/deep_freeze.test.ts @@ -27,16 +27,14 @@ it( 'types return values to prevent mutations in typescript', async () => { await expect( - execa.stdout('tsc', ['--noEmit'], { + execa('tsc', ['--noEmit'], { cwd: resolve(__dirname, '__fixtures__/frozen_object_mutation'), - }) - ).rejects.toThrowErrorMatchingInlineSnapshot(` -"Command failed: tsc --noEmit - -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. -" -`); + preferLocal: true, + }).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." + `); }, MINUTE ); diff --git a/src/dev/build/lib/exec.js b/src/dev/build/lib/exec.js index 57dced92ca625..82762f3bd03a9 100644 --- a/src/dev/build/lib/exec.js +++ b/src/dev/build/lib/exec.js @@ -36,6 +36,7 @@ export async function exec(log, cmd, args, options = {}) { stdio: ['ignore', 'pipe', 'pipe'], cwd, env, + preferLocal: true, }); await watchStdioForLine(proc, line => log[level](line), exitAfter); diff --git a/src/dev/build/lib/version_info.js b/src/dev/build/lib/version_info.js index b0f51eaaa1d79..8225000c13f07 100644 --- a/src/dev/build/lib/version_info.js +++ b/src/dev/build/lib/version_info.js @@ -28,7 +28,9 @@ async function getBuildNumber() { return log.stdout.split('\n').length; } - const wc = await execa.shell('git log --format="%h" | wc -l'); + const wc = await execa.command('git log --format="%h" | wc -l', { + shell: true + }); return parseFloat(wc.stdout.trim()); } @@ -39,7 +41,7 @@ export async function getVersionInfo({ isRelease, versionQualifier, pkg }) { ); return { - buildSha: await execa.stdout('git', ['rev-parse', 'HEAD']), + buildSha: (await execa('git', ['rev-parse', 'HEAD'])).stdout, buildVersion, buildNumber: await getBuildNumber(), }; diff --git a/src/dev/build/tasks/nodejs/__tests__/download.js b/src/dev/build/tasks/nodejs/__tests__/download.js index 551773a6f6325..c76ff15b89289 100644 --- a/src/dev/build/tasks/nodejs/__tests__/download.js +++ b/src/dev/build/tasks/nodejs/__tests__/download.js @@ -196,7 +196,7 @@ describe('src/dev/build/tasks/nodejs/download', () => { } catch (error) { expect(error) .to.have.property('message') - .contain('Unexpected status code 500'); + .contain('Request failed with status code 500'); expect(reqCount).to.be(6); } }); diff --git a/src/dev/build/tasks/nodejs/download.js b/src/dev/build/tasks/nodejs/download.js index 48313c0911c24..0a030aced0d42 100644 --- a/src/dev/build/tasks/nodejs/download.js +++ b/src/dev/build/tasks/nodejs/download.js @@ -22,7 +22,7 @@ import { dirname } from 'path'; import chalk from 'chalk'; import { createHash } from 'crypto'; -import wreck from '@hapi/wreck'; +import Axios from 'axios'; import { mkdirp } from '../../lib'; @@ -51,21 +51,24 @@ export async function download(options) { try { log.debug(`Attempting download of ${url}`, chalk.dim(sha256)); - const response = await wreck.request('GET', url); + const response = await Axios.request({ + url: url, + responseType: 'stream' + }); - if (response.statusCode !== 200) { - throw new Error(`Unexpected status code ${response.statusCode} when downloading ${url}`); + if (response.status !== 200) { + throw new Error(`Unexpected status code ${response.status} when downloading ${url}`); } const hash = createHash('sha256'); await new Promise((resolve, reject) => { - response.on('data', chunk => { + response.data.on('data', chunk => { hash.update(chunk); writeSync(fileHandle, chunk); }); - response.on('error', reject); - response.on('end', resolve); + response.data.on('error', reject); + response.data.on('end', resolve); }); const downloadedSha256 = hash.digest('hex'); diff --git a/src/dev/build/tasks/nodejs/node_download_info.js b/src/dev/build/tasks/nodejs/node_download_info.js index 7c4fd5fde7bed..33ffd042d85a3 100644 --- a/src/dev/build/tasks/nodejs/node_download_info.js +++ b/src/dev/build/tasks/nodejs/node_download_info.js @@ -27,7 +27,7 @@ export function getNodeDownloadInfo(config, platform) { ? 'win-x64/node.exe' : `node-v${version}-${arch}.tar.gz`; - const url = `https://nodejs.org/dist/v${version}/${downloadName}`; + const url = `https://us-central1-elastic-kibana-184716.cloudfunctions.net/kibana-ci-proxy-cache/dist/v${version}/${downloadName}`; const downloadPath = config.resolveFromRepo('.node_binaries', version, basename(downloadName)); const extractDir = config.resolveFromRepo('.node_binaries', version, arch); diff --git a/src/dev/build/tasks/nodejs/node_shasums.test.ts b/src/dev/build/tasks/nodejs/node_shasums.test.ts index ee91d2a370fb1..08ac823c7ebf0 100644 --- a/src/dev/build/tasks/nodejs/node_shasums.test.ts +++ b/src/dev/build/tasks/nodejs/node_shasums.test.ts @@ -60,7 +60,9 @@ c4edece2c0aa68e816c4e067f397eb12e9d0c81bb37b3d349dbaf47cf246b0b7 win-x86/node.l jest.mock('axios', () => ({ async get(url: string) { - expect(url).toBe('https://nodejs.org/dist/v8.9.4/SHASUMS256.txt'); + expect(url).toBe( + 'https://us-central1-elastic-kibana-184716.cloudfunctions.net/kibana-ci-proxy-cache/dist/v8.9.4/SHASUMS256.txt' + ); return { status: 200, data: mockResponse, diff --git a/src/dev/build/tasks/nodejs/node_shasums.ts b/src/dev/build/tasks/nodejs/node_shasums.ts index 1b8d01a9b1d94..e0926aa3e49e4 100644 --- a/src/dev/build/tasks/nodejs/node_shasums.ts +++ b/src/dev/build/tasks/nodejs/node_shasums.ts @@ -20,7 +20,7 @@ import axios from 'axios'; export async function getNodeShasums(nodeVersion: string) { - const url = `https://nodejs.org/dist/v${nodeVersion}/SHASUMS256.txt`; + const url = `https://us-central1-elastic-kibana-184716.cloudfunctions.net/kibana-ci-proxy-cache/dist/v${nodeVersion}/SHASUMS256.txt`; const { status, data } = await axios.get(url); diff --git a/src/dev/ci_setup/setup_env.sh b/src/dev/ci_setup/setup_env.sh index b9fe8fe77d122..805b77365e624 100644 --- a/src/dev/ci_setup/setup_env.sh +++ b/src/dev/ci_setup/setup_env.sh @@ -2,6 +2,10 @@ set -e +if [[ "$CI_ENV_SETUP" ]]; then + return 0 +fi + installNode=$1 dir="$(pwd)" @@ -53,10 +57,10 @@ nodeDir="$cacheDir/node/$nodeVersion" if [[ "$OS" == "win" ]]; then nodeBin="$HOME/node" - nodeUrl="https://nodejs.org/dist/v$nodeVersion/node-v$nodeVersion-win-x64.zip" + nodeUrl="https://us-central1-elastic-kibana-184716.cloudfunctions.net/kibana-ci-proxy-cache/dist/v$nodeVersion/node-v$nodeVersion-win-x64.zip" else nodeBin="$nodeDir/bin" - nodeUrl="https://nodejs.org/dist/v$nodeVersion/node-v$nodeVersion-linux-x64.tar.gz" + nodeUrl="https://us-central1-elastic-kibana-184716.cloudfunctions.net/kibana-ci-proxy-cache/dist/v$nodeVersion/node-v$nodeVersion-linux-x64.tar.gz" fi if [[ "$installNode" == "true" ]]; then @@ -75,11 +79,11 @@ if [[ "$installNode" == "true" ]]; then mkdir -p "$nodeDir" if [[ "$OS" == "win" ]]; then nodePkg="$nodeDir/${nodeUrl##*/}" - curl --silent -o "$nodePkg" "$nodeUrl" + curl --silent -L -o "$nodePkg" "$nodeUrl" unzip -qo "$nodePkg" -d "$nodeDir" mv "${nodePkg%.*}" "$nodeBin" else - curl --silent "$nodeUrl" | tar -xz -C "$nodeDir" --strip-components=1 + curl --silent -L "$nodeUrl" | tar -xz -C "$nodeDir" --strip-components=1 fi fi fi @@ -152,3 +156,5 @@ if [[ -d "$ES_DIR" && -f "$ES_JAVA_PROP_PATH" ]]; then echo "Setting JAVA_HOME=$HOME/.java/$ES_BUILD_JAVA" export JAVA_HOME=$HOME/.java/$ES_BUILD_JAVA fi + +export CI_ENV_SETUP=true diff --git a/src/dev/i18n/extractors/code.js b/src/dev/i18n/extractors/code.js index 58ca059be5491..fa0d834824e97 100644 --- a/src/dev/i18n/extractors/code.js +++ b/src/dev/i18n/extractors/code.js @@ -67,7 +67,7 @@ export function* extractCodeMessages(buffer, reporter) { try { ast = parse(buffer.toString(), { sourceType: 'module', - plugins: ['jsx', 'typescript', 'objectRestSpread', 'classProperties', 'asyncGenerators'], + plugins: ['jsx', 'typescript', 'objectRestSpread', 'classProperties', 'asyncGenerators', 'dynamicImport'], }); } catch (error) { if (error instanceof SyntaxError) { diff --git a/src/dev/jest/integration_tests/junit_reporter.test.js b/src/dev/jest/integration_tests/junit_reporter.test.js index c26c8cfad8025..ed5d73cd87c40 100644 --- a/src/dev/jest/integration_tests/junit_reporter.test.js +++ b/src/dev/jest/integration_tests/junit_reporter.test.js @@ -52,7 +52,7 @@ it( } ); - expect(result.code).toBe(1); + expect(result.exitCode).toBe(1); await expect(parseXml(readFileSync(XML_PATH, 'utf8'))).resolves.toEqual({ testsuites: { $: { diff --git a/src/dev/license_checker/config.ts b/src/dev/license_checker/config.ts index 727c51f704431..fbd16d95ded1c 100644 --- a/src/dev/license_checker/config.ts +++ b/src/dev/license_checker/config.ts @@ -106,8 +106,5 @@ export const LICENSE_OVERRIDES = { // TODO can be removed once we upgrade the use of walk dependency past or equal to v2.3.14 'walk@2.3.9': ['MIT'], - // TODO remove this once we upgrade past or equal to v1.0.2 - 'babel-plugin-mock-imports@1.0.1': ['MIT'], - '@elastic/node-ctags@1.0.2': ['Nuclide software'], }; diff --git a/src/dev/precommit_hook/casing_check_config.js b/src/dev/precommit_hook/casing_check_config.js index 9829de2fd3920..edd818e1b42de 100644 --- a/src/dev/precommit_hook/casing_check_config.js +++ b/src/dev/precommit_hook/casing_check_config.js @@ -43,8 +43,9 @@ export const IGNORE_FILE_GLOBS = [ 'x-pack/docs/**/*', 'src/legacy/ui/public/assets/fonts/**/*', 'packages/kbn-utility-types/test-d/**/*', - 'Jenkinsfile', + '**/Jenkinsfile*', 'Dockerfile*', + 'vars/*', // Files in this directory must match a pre-determined name in some cases. 'x-pack/legacy/plugins/canvas/.storybook/*', diff --git a/src/dev/prs/run_update_prs_cli.ts b/src/dev/prs/run_update_prs_cli.ts index 6d99ac5fa45f3..bb7f50758a28c 100644 --- a/src/dev/prs/run_update_prs_cli.ts +++ b/src/dev/prs/run_update_prs_cli.ts @@ -86,7 +86,7 @@ run( // attempt to init upstream remote await execInDir('git', ['remote', 'add', 'upstream', UPSTREAM_URL]); } catch (error) { - if (error.code !== 128) { + if (error.exitCode !== 128) { throw error; } diff --git a/src/dev/run_check_core_api_changes.ts b/src/dev/run_check_core_api_changes.ts index d2c75c86ce744..ccf92cc432d2b 100644 --- a/src/dev/run_check_core_api_changes.ts +++ b/src/dev/run_check_core_api_changes.ts @@ -76,12 +76,16 @@ const apiExtractorConfig = (folder: string): ExtractorConfig => { }; const runBuildTypes = async () => { - await execa.shell('yarn run build:types'); + await execa('yarn', ['run', 'build:types']); }; const runApiDocumenter = async (folder: string) => { - await execa.shell( - `api-documenter markdown -i ./build/${folder} -o ./docs/development/core/${folder}` + await execa( + 'api-documenter', + ['markdown', '-i', `./build/${folder}`, '-o', `./docs/development/core/${folder}`], + { + preferLocal: true, + } ); }; diff --git a/src/dev/typescript/exec_in_projects.ts b/src/dev/typescript/exec_in_projects.ts index 8895e964c7817..a34f2bdd28670 100644 --- a/src/dev/typescript/exec_in_projects.ts +++ b/src/dev/typescript/exec_in_projects.ts @@ -45,6 +45,7 @@ export function execInProjects( cwd: process.cwd(), env: chalk.enabled ? { FORCE_COLOR: 'true' } : {}, stdio: ['ignore', 'pipe', 'pipe'], + preferLocal: true, }).catch(error => { throw new ProjectFailure(project, error); }), diff --git a/src/dev/typescript/run_check_ts_projects_cli.ts b/src/dev/typescript/run_check_ts_projects_cli.ts index f42a4e2759370..85f3d473dce6b 100644 --- a/src/dev/typescript/run_check_ts_projects_cli.ts +++ b/src/dev/typescript/run_check_ts_projects_cli.ts @@ -30,7 +30,7 @@ import { PROJECTS } from './projects'; export async function runCheckTsProjectsCli() { run( async ({ log }) => { - const files = await execa.stdout('git', ['ls-tree', '--name-only', '-r', 'HEAD'], { + const { stdout: files } = await execa('git', ['ls-tree', '--name-only', '-r', 'HEAD'], { cwd: REPO_ROOT, }); diff --git a/src/dev/typescript/run_type_check_cli.ts b/src/dev/typescript/run_type_check_cli.ts index bf6e8711fa81e..3deebcc0c18f9 100644 --- a/src/dev/typescript/run_type_check_cli.ts +++ b/src/dev/typescript/run_type_check_cli.ts @@ -88,7 +88,7 @@ export function runTypeCheckCli() { } execInProjects(log, projects, process.execPath, project => [ - ...(project.name === 'x-pack' ? ['--max-old-space-size=2048'] : []), + ...(project.name === 'x-pack' ? ['--max-old-space-size=4096'] : []), require.resolve('typescript/bin/tsc'), ...['--project', project.tsConfigPath], ...tscArgs, diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/main/main.tsx b/src/legacy/core_plugins/console/np_ready/public/application/containers/main/main.tsx index 82256cf739882..4e5afbdb5821e 100644 --- a/src/legacy/core_plugins/console/np_ready/public/application/containers/main/main.tsx +++ b/src/legacy/core_plugins/console/np_ready/public/application/containers/main/main.tsx @@ -19,7 +19,8 @@ import React, { useCallback, useState } from 'react'; import { debounce } from 'lodash'; -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; import { EditorOutput, Editor, ConsoleHistory } from '../editor'; import { Settings } from '../settings'; @@ -77,6 +78,13 @@ export function Main() { responsive={false} > + +

+ {i18n.translate('console.pageHeading', { + defaultMessage: 'Console', + })} +

+
setShowHistory(!showingHistory), diff --git a/src/legacy/core_plugins/console/np_ready/public/application/models/legacy_editor.test.ts b/src/legacy/core_plugins/console/np_ready/public/application/models/legacy_editor.test.ts new file mode 100644 index 0000000000000..0dc1bcc96ddee --- /dev/null +++ b/src/legacy/core_plugins/console/np_ready/public/application/models/legacy_editor.test.ts @@ -0,0 +1,69 @@ +/* + * 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 { Range as AceRange } from 'brace'; +import { LegacyEditor } from './legacy_editor'; + +describe('Legacy Editor', () => { + const aceMock: any = { + getValue() { + return 'ok'; + }, + + getCursorPosition() { + return { + row: 1, + column: 1, + }; + }, + + getSession() { + return { + replace(range: AceRange, value: string) {}, + getLine(n: number) { + return 'line'; + }, + doc: { + getTextRange(r: any) { + return ''; + }, + }, + getState(n: number) { + return n; + }, + }; + }, + }; + + // This is to ensure that we are correctly importing Ace's Range component + it('smoke tests for updates to ranges', () => { + const legacyEditor = new LegacyEditor(aceMock); + legacyEditor.getValueInRange({ + start: { lineNumber: 1, column: 1 }, + end: { lineNumber: 2, column: 2 }, + }); + legacyEditor.replace( + { + start: { lineNumber: 1, column: 1 }, + end: { lineNumber: 2, column: 2 }, + }, + 'test!' + ); + }); +}); diff --git a/src/legacy/core_plugins/console/np_ready/public/application/models/legacy_editor.ts b/src/legacy/core_plugins/console/np_ready/public/application/models/legacy_editor.ts index 1b083adcfea76..f8c3f425a1032 100644 --- a/src/legacy/core_plugins/console/np_ready/public/application/models/legacy_editor.ts +++ b/src/legacy/core_plugins/console/np_ready/public/application/models/legacy_editor.ts @@ -17,10 +17,13 @@ * under the License. */ -import { Editor as IAceEditor, Range as AceRange } from 'brace'; +import ace from 'brace'; +import { Editor as IAceEditor } from 'brace'; import { CoreEditor, Position, Range, Token, TokensProvider } from '../../types'; import { AceTokensProvider } from '../../lib/ace_token_provider'; +const _AceRange = ace.acequire('ace/range').Range; + export class LegacyEditor implements CoreEditor { constructor(private readonly editor: IAceEditor) {} @@ -31,7 +34,7 @@ export class LegacyEditor implements CoreEditor { getValueInRange({ start, end }: Range): string { const session = this.editor.getSession(); - const aceRange = new AceRange( + const aceRange = new _AceRange( start.lineNumber - 1, start.column - 1, end.lineNumber - 1, @@ -90,7 +93,7 @@ export class LegacyEditor implements CoreEditor { } replace({ start, end }: Range, value: string): void { - const aceRange = new AceRange( + const aceRange = new _AceRange( start.lineNumber - 1, start.column - 1, end.lineNumber - 1, diff --git a/src/legacy/core_plugins/console/np_ready/public/legacy.ts b/src/legacy/core_plugins/console/np_ready/public/legacy.ts index af9803c97749e..1a2d312823f6f 100644 --- a/src/legacy/core_plugins/console/np_ready/public/legacy.ts +++ b/src/legacy/core_plugins/console/np_ready/public/legacy.ts @@ -30,7 +30,6 @@ import uiRoutes from 'ui/routes'; import { DOC_LINK_VERSION } from 'ui/documentation_links'; import { I18nContext } from 'ui/i18n'; import { ResizeChecker } from 'ui/resize_checker'; -import 'ui/autoload/styles'; import 'ui/capabilities/route_setup'; /* eslint-enable @kbn/eslint/no-restricted-paths */ diff --git a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete.ts b/src/legacy/core_plugins/console/public/quarantined/src/autocomplete.ts index 41869f41c222f..47edf42f0eec5 100644 --- a/src/legacy/core_plugins/console/public/quarantined/src/autocomplete.ts +++ b/src/legacy/core_plugins/console/public/quarantined/src/autocomplete.ts @@ -317,16 +317,16 @@ export default function({ coreEditor: editor, parser, execCommand, - getCursor, - isCompleteActive, + getCursorPosition, + isCompleterActive, addChangeListener, removeChangeListener, }: { coreEditor: LegacyEditor; parser: any; execCommand: (cmd: string) => void; - getCursor: () => any; - isCompleteActive: () => boolean; + getCursorPosition: () => Position | null; + isCompleterActive: () => boolean; addChangeListener: (fn: any) => void; removeChangeListener: (fn: any) => void; }) { @@ -969,11 +969,10 @@ export default function({ 100); function editorChangeListener() { - const cursor = getCursor(); - if (isCompleteActive()) { - return; + const position = getCursorPosition(); + if (position && !isCompleterActive()) { + evaluateCurrentTokenAfterAChange(position); } - evaluateCurrentTokenAfterAChange(cursor); } function getCompletions( diff --git a/src/legacy/core_plugins/console/public/quarantined/src/input.ts b/src/legacy/core_plugins/console/public/quarantined/src/input.ts index a5e38e7a06e0e..eb93f8e165cb5 100644 --- a/src/legacy/core_plugins/console/public/quarantined/src/input.ts +++ b/src/legacy/core_plugins/console/public/quarantined/src/input.ts @@ -24,6 +24,7 @@ import { LegacyEditor } from '../../../np_ready/public/application/models'; // @ts-ignore import SenseEditor from './sense_editor/editor'; +import { Position } from '../../../np_ready/public/types'; let input: any; export function initializeEditor($el: JQuery, $actionsEl: JQuery) { @@ -35,8 +36,18 @@ export function initializeEditor($el: JQuery, $actionsEl: JQuery input.execCommand(cmd), - getCursor: () => input.selection.lead, - isCompleteActive: () => input.__ace.completer && input.__ace.completer.activated, + getCursorPosition: (): Position | null => { + if (input.selection && input.selection.lead) { + return { + lineNumber: input.selection.lead.row + 1, + column: input.selection.lead.column + 1, + }; + } + return null; + }, + isCompleterActive: () => { + return Boolean(input.__ace.completer && input.__ace.completer.activated); + }, addChangeListener: (fn: any) => input.on('changeSelection', fn), removeChangeListener: (fn: any) => input.off('changeSelection', fn), }; diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/index.ts b/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/index.ts index cbbbd41827853..1dad1b488590e 100644 --- a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/index.ts +++ b/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/index.ts @@ -17,7 +17,7 @@ * under the License. */ -import { PluginInitializerContext } from 'kibana/public'; +import { PluginInitializerContext } from '../../../../../../core/public'; import { DashboardEmbeddableContainerPublicPlugin } from './plugin'; export * from './lib'; diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/actions/index.ts b/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/actions/index.ts index b0707610cf21b..6c0db82fbbc5b 100644 --- a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/actions/index.ts +++ b/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/actions/index.ts @@ -18,3 +18,4 @@ */ export { ExpandPanelAction, EXPAND_PANEL_ACTION } from './expand_panel_action'; +export { ReplacePanelAction, REPLACE_PANEL_ACTION } from './replace_panel_action'; diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/actions/open_replace_panel_flyout.tsx b/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/actions/open_replace_panel_flyout.tsx new file mode 100644 index 0000000000000..b6652caf3ab83 --- /dev/null +++ b/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/actions/open_replace_panel_flyout.tsx @@ -0,0 +1,56 @@ +/* + * 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 { CoreStart } from 'src/core/public'; +import { ReplacePanelFlyout } from './replace_panel_flyout'; + +import { + IEmbeddable, + EmbeddableInput, + EmbeddableOutput, +} from '../../../../../../embeddable_api/public/np_ready/public'; + +import { IContainer } from '../../../../../../embeddable_api/public/np_ready/public'; +import { NotificationsStart } from '../../../../../../../../core/public'; + +export async function openReplacePanelFlyout(options: { + embeddable: IContainer; + core: CoreStart; + savedObjectFinder: React.ComponentType; + notifications: NotificationsStart; + panelToRemove: IEmbeddable; +}) { + const { embeddable, core, panelToRemove, savedObjectFinder, notifications } = options; + const flyoutSession = core.overlays.openFlyout( + { + if (flyoutSession) { + flyoutSession.close(); + } + }} + panelToRemove={panelToRemove} + savedObjectsFinder={savedObjectFinder} + notifications={notifications} + />, + { + 'data-test-subj': 'replacePanelFlyout', + } + ); +} diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/actions/replace_panel_action.test.tsx b/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/actions/replace_panel_action.test.tsx new file mode 100644 index 0000000000000..e1d2e3609570c --- /dev/null +++ b/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/actions/replace_panel_action.test.tsx @@ -0,0 +1,129 @@ +/* + * 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 { isErrorEmbeddable, EmbeddableFactory } from '../embeddable_api'; +import { ReplacePanelAction } from './replace_panel_action'; +import { DashboardContainer } from '../embeddable'; +import { getSampleDashboardInput, getSampleDashboardPanel } from '../test_helpers'; +import { + CONTACT_CARD_EMBEDDABLE, + ContactCardEmbeddableFactory, + ContactCardEmbeddable, + ContactCardEmbeddableInput, + ContactCardEmbeddableOutput, +} from '../../../../../../embeddable_api/public/np_ready/public/lib/test_samples'; +import { DashboardOptions } from '../embeddable/dashboard_container_factory'; + +const embeddableFactories = new Map(); +embeddableFactories.set( + CONTACT_CARD_EMBEDDABLE, + new ContactCardEmbeddableFactory({} as any, (() => null) as any, {} as any) +); + +let container: DashboardContainer; +let embeddable: ContactCardEmbeddable; + +beforeEach(async () => { + const options: DashboardOptions = { + ExitFullScreenButton: () => null, + SavedObjectFinder: () => null, + application: {} as any, + embeddable: { + getEmbeddableFactory: (id: string) => embeddableFactories.get(id)!, + } as any, + inspector: {} as any, + notifications: {} as any, + overlays: {} as any, + savedObjectMetaData: {} as any, + uiActions: {} as any, + }; + const input = getSampleDashboardInput({ + panels: { + '123': getSampleDashboardPanel({ + explicitInput: { firstName: 'Sam', id: '123' }, + type: CONTACT_CARD_EMBEDDABLE, + }), + }, + }); + container = new DashboardContainer(input, options); + + const contactCardEmbeddable = await container.addNewEmbeddable< + ContactCardEmbeddableInput, + ContactCardEmbeddableOutput, + ContactCardEmbeddable + >(CONTACT_CARD_EMBEDDABLE, { + firstName: 'Kibana', + }); + + if (isErrorEmbeddable(contactCardEmbeddable)) { + throw new Error('Failed to create embeddable'); + } else { + embeddable = contactCardEmbeddable; + } +}); + +test('Executes the replace panel action', async () => { + let core: any; + let SavedObjectFinder: any; + let notifications: any; + const action = new ReplacePanelAction(core, SavedObjectFinder, notifications); + action.execute({ embeddable }); +}); + +test('Is not compatible when embeddable is not in a dashboard container', async () => { + let core: any; + let SavedObjectFinder: any; + let notifications: any; + const action = new ReplacePanelAction(core, SavedObjectFinder, notifications); + expect( + await action.isCompatible({ + embeddable: new ContactCardEmbeddable( + { firstName: 'sue', id: '123' }, + { execAction: (() => null) as any } + ), + }) + ).toBe(false); +}); + +test('Execute throws an error when called with an embeddable not in a parent', async () => { + let core: any; + let SavedObjectFinder: any; + let notifications: any; + const action = new ReplacePanelAction(core, SavedObjectFinder, notifications); + async function check() { + await action.execute({ embeddable: container }); + } + await expect(check()).rejects.toThrow(Error); +}); + +test('Returns title', async () => { + let core: any; + let SavedObjectFinder: any; + let notifications: any; + const action = new ReplacePanelAction(core, SavedObjectFinder, notifications); + expect(action.getDisplayName({ embeddable })).toBeDefined(); +}); + +test('Returns an icon', async () => { + let core: any; + let SavedObjectFinder: any; + let notifications: any; + const action = new ReplacePanelAction(core, SavedObjectFinder, notifications); + expect(action.getIconType({ embeddable })).toBeDefined(); +}); diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/actions/replace_panel_action.tsx b/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/actions/replace_panel_action.tsx new file mode 100644 index 0000000000000..f36efc498b15f --- /dev/null +++ b/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/actions/replace_panel_action.tsx @@ -0,0 +1,94 @@ +/* + * 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 { CoreStart } from 'src/core/public'; + +import { IEmbeddable, ViewMode } from '../../../../../../embeddable_api/public/np_ready/public'; +import { DASHBOARD_CONTAINER_TYPE, DashboardContainer } from '../embeddable'; +import { + IAction, + IncompatibleActionError, +} from '../../../../../../../../plugins/ui_actions/public'; +import { NotificationsStart } from '../../../../../../../../core/public'; +import { openReplacePanelFlyout } from './open_replace_panel_flyout'; + +export const REPLACE_PANEL_ACTION = 'replacePanel'; + +function isDashboard(embeddable: IEmbeddable): embeddable is DashboardContainer { + return embeddable.type === DASHBOARD_CONTAINER_TYPE; +} + +interface ActionContext { + embeddable: IEmbeddable; +} + +export class ReplacePanelAction implements IAction { + public readonly type = REPLACE_PANEL_ACTION; + public readonly id = REPLACE_PANEL_ACTION; + public order = 11; + + constructor( + private core: CoreStart, + private savedobjectfinder: React.ComponentType, + private notifications: NotificationsStart + ) {} + + public getDisplayName({ embeddable }: ActionContext) { + if (!embeddable.parent || !isDashboard(embeddable.parent)) { + throw new IncompatibleActionError(); + } + return i18n.translate('dashboardEmbeddableContainer.panel.removePanel.replacePanel', { + defaultMessage: 'Replace panel', + }); + } + + public getIconType({ embeddable }: ActionContext) { + if (!embeddable.parent || !isDashboard(embeddable.parent)) { + throw new IncompatibleActionError(); + } + return 'kqlOperand'; + } + + public async isCompatible({ embeddable }: ActionContext) { + if (embeddable.getInput().viewMode) { + if (embeddable.getInput().viewMode === ViewMode.VIEW) { + return false; + } + } + + return Boolean(embeddable.parent && isDashboard(embeddable.parent)); + } + + public async execute({ embeddable }: ActionContext) { + if (!embeddable.parent || !isDashboard(embeddable.parent)) { + throw new IncompatibleActionError(); + } + + const view = embeddable; + const dash = embeddable.parent; + openReplacePanelFlyout({ + embeddable: dash, + core: this.core, + savedObjectFinder: this.savedobjectfinder, + notifications: this.notifications, + panelToRemove: view, + }); + } +} diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/actions/replace_panel_flyout.tsx b/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/actions/replace_panel_flyout.tsx new file mode 100644 index 0000000000000..0e738556372ce --- /dev/null +++ b/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/actions/replace_panel_flyout.tsx @@ -0,0 +1,146 @@ +/* + * 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 React from 'react'; + +import { NotificationsStart } from 'src/core/public'; +import { DashboardPanelState } from 'src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public'; + +import { + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutHeader, + EuiTitle, + EuiGlobalToastListToast as Toast, +} from '@elastic/eui'; + +import { IContainer } from '../../../../../../embeddable_api/public/np_ready/public'; +import { + IEmbeddable, + EmbeddableInput, + EmbeddableOutput, +} from '../../../../../../embeddable_api/public/np_ready/public'; + +import { start } from '../../../../../../embeddable_api/public/np_ready/public/legacy'; + +interface Props { + container: IContainer; + savedObjectsFinder: React.ComponentType; + onClose: () => void; + notifications: NotificationsStart; + panelToRemove: IEmbeddable; +} + +export class ReplacePanelFlyout extends React.Component { + private lastToast: Toast = { + id: 'panelReplaceToast', + }; + + constructor(props: Props) { + super(props); + } + + public showToast = (name: string) => { + // To avoid the clutter of having toast messages cover flyout + // close previous toast message before creating a new one + if (this.lastToast) { + this.props.notifications.toasts.remove(this.lastToast); + } + + this.lastToast = this.props.notifications.toasts.addSuccess({ + title: i18n.translate( + 'dashboardEmbeddableContainer.addPanel.savedObjectAddedToContainerSuccessMessageTitle', + { + defaultMessage: '{savedObjectName} was added', + values: { + savedObjectName: name, + }, + } + ), + 'data-test-subj': 'addObjectToContainerSuccess', + }); + }; + + public onReplacePanel = async (id: string, type: string, name: string) => { + const originalPanels = this.props.container.getInput().panels; + const filteredPanels = { ...originalPanels }; + + const nnw = (filteredPanels[this.props.panelToRemove.id] as DashboardPanelState).gridData.w; + const nnh = (filteredPanels[this.props.panelToRemove.id] as DashboardPanelState).gridData.h; + const nnx = (filteredPanels[this.props.panelToRemove.id] as DashboardPanelState).gridData.x; + const nny = (filteredPanels[this.props.panelToRemove.id] as DashboardPanelState).gridData.y; + + // add the new view + const newObj = await this.props.container.addSavedObjectEmbeddable(type, id); + + const finalPanels = this.props.container.getInput().panels; + (finalPanels[newObj.id] as DashboardPanelState).gridData.w = nnw; + (finalPanels[newObj.id] as DashboardPanelState).gridData.h = nnh; + (finalPanels[newObj.id] as DashboardPanelState).gridData.x = nnx; + (finalPanels[newObj.id] as DashboardPanelState).gridData.y = nny; + + // delete the old view + delete finalPanels[this.props.panelToRemove.id]; + + // apply changes + this.props.container.updateInput(finalPanels); + this.props.container.reload(); + + this.showToast(name); + this.props.onClose(); + }; + + public render() { + const SavedObjectFinder = this.props.savedObjectsFinder; + const savedObjectsFinder = ( + + Boolean(embeddableFactory.savedObjectMetaData) && !embeddableFactory.isContainerType + ) + .map(({ savedObjectMetaData }) => savedObjectMetaData as any)} + showFilter={true} + onChoose={this.onReplacePanel} + /> + ); + + const panelToReplace = 'Replace panel ' + this.props.panelToRemove.getTitle() + ' with:'; + + return ( + + + +

+ {panelToReplace} +

+
+
+ {savedObjectsFinder} +
+ ); + } +} diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/plugin.ts b/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/plugin.ts index 3d243ab3aa37a..bb18d109b0de7 100644 --- a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/plugin.ts +++ b/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/plugin.ts @@ -20,7 +20,7 @@ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'src/core/public'; import { IUiActionsSetup, IUiActionsStart } from '../../../../../../plugins/ui_actions/public'; import { CONTEXT_MENU_TRIGGER, Plugin as EmbeddablePlugin } from './lib/embeddable_api'; -import { ExpandPanelAction, DashboardContainerFactory } from './lib'; +import { ExpandPanelAction, ReplacePanelAction, DashboardContainerFactory } from './lib'; import { Start as InspectorStartContract } from '../../../../../../plugins/inspector/public'; interface SetupDependencies { @@ -55,6 +55,14 @@ export class DashboardEmbeddableContainerPublicPlugin const { application, notifications, overlays } = core; const { embeddable, inspector, __LEGACY, uiActions } = plugins; + const changeViewAction = new ReplacePanelAction( + core, + __LEGACY.SavedObjectFinder, + notifications + ); + uiActions.registerAction(changeViewAction); + uiActions.attachAction(CONTEXT_MENU_TRIGGER, changeViewAction.id); + const factory = new DashboardContainerFactory({ application, notifications, diff --git a/src/legacy/core_plugins/data/public/index_patterns/services.ts b/src/legacy/core_plugins/data/public/index_patterns/services.ts index 3782f494e8178..5cc087548d6fb 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/services.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/services.ts @@ -18,21 +18,7 @@ */ import { NotificationsStart } from 'src/core/public'; - -const createGetterSetter = (name: string): [() => T, (value: T) => void] => { - let value: T; - - const get = (): T => { - if (!value) throw new Error(`${name} was not set`); - return value; - }; - - const set = (newValue: T) => { - value = newValue; - }; - - return [get, set]; -}; +import { createGetterSetter } from '../../../../../plugins/kibana_utils/public'; export const [getNotifications, setNotifications] = createGetterSetter( 'Notifications' diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/__snapshots__/query_bar_input.test.tsx.snap b/src/legacy/core_plugins/data/public/query/query_bar/components/__snapshots__/query_bar_input.test.tsx.snap index 06f9e6081e522..286e60cca9712 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/components/__snapshots__/query_bar_input.test.tsx.snap +++ b/src/legacy/core_plugins/data/public/query/query_bar/components/__snapshots__/query_bar_input.test.tsx.snap @@ -103,6 +103,13 @@ exports[`QueryBarInput Should disable autoFocus on EuiFieldText when disableAuto }, "chrome": Object { "addApplicationClass": [MockFunction], + "docTitle": Object { + "__legacy": Object { + "setBaseTitle": [MockFunction], + }, + "change": [MockFunction], + "reset": [MockFunction], + }, "getApplicationClasses$": [MockFunction], "getBadge$": [MockFunction], "getBrand$": [MockFunction], @@ -229,10 +236,20 @@ exports[`QueryBarInput Should disable autoFocus on EuiFieldText when disableAuto }, "http": Object { "addLoadingCount": [MockFunction], - "basePath": Object { - "get": [MockFunction], - "prepend": [MockFunction], - "remove": [MockFunction], + "anonymousPaths": AnonymousPaths { + "basePath": BasePath { + "basePath": "", + "get": [Function], + "prepend": [Function], + "remove": [Function], + }, + "paths": Set {}, + }, + "basePath": BasePath { + "basePath": "", + "get": [Function], + "prepend": [Function], + "remove": [Function], }, "delete": [MockFunction], "fetch": [MockFunction], @@ -649,6 +666,13 @@ exports[`QueryBarInput Should disable autoFocus on EuiFieldText when disableAuto }, "chrome": Object { "addApplicationClass": [MockFunction], + "docTitle": Object { + "__legacy": Object { + "setBaseTitle": [MockFunction], + }, + "change": [MockFunction], + "reset": [MockFunction], + }, "getApplicationClasses$": [MockFunction], "getBadge$": [MockFunction], "getBrand$": [MockFunction], @@ -775,10 +799,20 @@ exports[`QueryBarInput Should disable autoFocus on EuiFieldText when disableAuto }, "http": Object { "addLoadingCount": [MockFunction], - "basePath": Object { - "get": [MockFunction], - "prepend": [MockFunction], - "remove": [MockFunction], + "anonymousPaths": AnonymousPaths { + "basePath": BasePath { + "basePath": "", + "get": [Function], + "prepend": [Function], + "remove": [Function], + }, + "paths": Set {}, + }, + "basePath": BasePath { + "basePath": "", + "get": [Function], + "prepend": [Function], + "remove": [Function], }, "delete": [MockFunction], "fetch": [MockFunction], @@ -1183,6 +1217,13 @@ exports[`QueryBarInput Should pass the query language to the language switcher 1 }, "chrome": Object { "addApplicationClass": [MockFunction], + "docTitle": Object { + "__legacy": Object { + "setBaseTitle": [MockFunction], + }, + "change": [MockFunction], + "reset": [MockFunction], + }, "getApplicationClasses$": [MockFunction], "getBadge$": [MockFunction], "getBrand$": [MockFunction], @@ -1309,10 +1350,20 @@ exports[`QueryBarInput Should pass the query language to the language switcher 1 }, "http": Object { "addLoadingCount": [MockFunction], - "basePath": Object { - "get": [MockFunction], - "prepend": [MockFunction], - "remove": [MockFunction], + "anonymousPaths": AnonymousPaths { + "basePath": BasePath { + "basePath": "", + "get": [Function], + "prepend": [Function], + "remove": [Function], + }, + "paths": Set {}, + }, + "basePath": BasePath { + "basePath": "", + "get": [Function], + "prepend": [Function], + "remove": [Function], }, "delete": [MockFunction], "fetch": [MockFunction], @@ -1726,6 +1777,13 @@ exports[`QueryBarInput Should pass the query language to the language switcher 1 }, "chrome": Object { "addApplicationClass": [MockFunction], + "docTitle": Object { + "__legacy": Object { + "setBaseTitle": [MockFunction], + }, + "change": [MockFunction], + "reset": [MockFunction], + }, "getApplicationClasses$": [MockFunction], "getBadge$": [MockFunction], "getBrand$": [MockFunction], @@ -1852,10 +1910,20 @@ exports[`QueryBarInput Should pass the query language to the language switcher 1 }, "http": Object { "addLoadingCount": [MockFunction], - "basePath": Object { - "get": [MockFunction], - "prepend": [MockFunction], - "remove": [MockFunction], + "anonymousPaths": AnonymousPaths { + "basePath": BasePath { + "basePath": "", + "get": [Function], + "prepend": [Function], + "remove": [Function], + }, + "paths": Set {}, + }, + "basePath": BasePath { + "basePath": "", + "get": [Function], + "prepend": [Function], + "remove": [Function], }, "delete": [MockFunction], "fetch": [MockFunction], @@ -2260,6 +2328,13 @@ exports[`QueryBarInput Should render the given query 1`] = ` }, "chrome": Object { "addApplicationClass": [MockFunction], + "docTitle": Object { + "__legacy": Object { + "setBaseTitle": [MockFunction], + }, + "change": [MockFunction], + "reset": [MockFunction], + }, "getApplicationClasses$": [MockFunction], "getBadge$": [MockFunction], "getBrand$": [MockFunction], @@ -2386,10 +2461,20 @@ exports[`QueryBarInput Should render the given query 1`] = ` }, "http": Object { "addLoadingCount": [MockFunction], - "basePath": Object { - "get": [MockFunction], - "prepend": [MockFunction], - "remove": [MockFunction], + "anonymousPaths": AnonymousPaths { + "basePath": BasePath { + "basePath": "", + "get": [Function], + "prepend": [Function], + "remove": [Function], + }, + "paths": Set {}, + }, + "basePath": BasePath { + "basePath": "", + "get": [Function], + "prepend": [Function], + "remove": [Function], }, "delete": [MockFunction], "fetch": [MockFunction], @@ -2803,6 +2888,13 @@ exports[`QueryBarInput Should render the given query 1`] = ` }, "chrome": Object { "addApplicationClass": [MockFunction], + "docTitle": Object { + "__legacy": Object { + "setBaseTitle": [MockFunction], + }, + "change": [MockFunction], + "reset": [MockFunction], + }, "getApplicationClasses$": [MockFunction], "getBadge$": [MockFunction], "getBrand$": [MockFunction], @@ -2929,10 +3021,20 @@ exports[`QueryBarInput Should render the given query 1`] = ` }, "http": Object { "addLoadingCount": [MockFunction], - "basePath": Object { - "get": [MockFunction], - "prepend": [MockFunction], - "remove": [MockFunction], + "anonymousPaths": AnonymousPaths { + "basePath": BasePath { + "basePath": "", + "get": [Function], + "prepend": [Function], + "remove": [Function], + }, + "paths": Set {}, + }, + "basePath": BasePath { + "basePath": "", + "get": [Function], + "prepend": [Function], + "remove": [Function], }, "delete": [MockFunction], "fetch": [MockFunction], diff --git a/src/legacy/core_plugins/expressions/index.ts b/src/legacy/core_plugins/expressions/index.ts index b10e9a8dd5442..4ba9a74795d4c 100644 --- a/src/legacy/core_plugins/expressions/index.ts +++ b/src/legacy/core_plugins/expressions/index.ts @@ -34,6 +34,7 @@ export default function DataExpressionsPlugin(kibana: any) { init: (server: Legacy.Server) => ({}), uiExports: { injectDefaultVars: () => ({}), + styleSheetPaths: resolve(__dirname, 'public/index.scss'), }, }; diff --git a/src/legacy/core_plugins/expressions/public/index.scss b/src/legacy/core_plugins/expressions/public/index.scss new file mode 100644 index 0000000000000..6efc552bf319b --- /dev/null +++ b/src/legacy/core_plugins/expressions/public/index.scss @@ -0,0 +1,13 @@ +// Import the EUI global scope so we can use EUI constants +@import 'src/legacy/ui/public/styles/_styling_constants'; + +/* Expressions plugin styles */ + +// Prefix all styles with "exp" to avoid conflicts. +// Examples +// expChart +// expChart__legend +// expChart__legend--small +// expChart__legend-isLoading + +@import './np_ready/public/index'; diff --git a/src/legacy/core_plugins/expressions/public/np_ready/public/_expression_renderer.scss b/src/legacy/core_plugins/expressions/public/np_ready/public/_expression_renderer.scss new file mode 100644 index 0000000000000..4f030384ed883 --- /dev/null +++ b/src/legacy/core_plugins/expressions/public/np_ready/public/_expression_renderer.scss @@ -0,0 +1,20 @@ +.expExpressionRenderer { + position: relative; + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; +} + +.expExpressionRenderer__expression { + width: 100%; + height: 100%; +} + +.expExpressionRenderer-isEmpty, +.expExpressionRenderer-hasError { + .expExpressionRenderer__expression { + display: none; + } +} diff --git a/src/legacy/core_plugins/expressions/public/np_ready/public/_index.scss b/src/legacy/core_plugins/expressions/public/np_ready/public/_index.scss new file mode 100644 index 0000000000000..b9df491cd6e79 --- /dev/null +++ b/src/legacy/core_plugins/expressions/public/np_ready/public/_index.scss @@ -0,0 +1 @@ +@import './expression_renderer'; diff --git a/src/legacy/core_plugins/expressions/public/np_ready/public/execute.ts b/src/legacy/core_plugins/expressions/public/np_ready/public/execute.ts index b4b11588b91bf..8043e0fb6e3f9 100644 --- a/src/legacy/core_plugins/expressions/public/np_ready/public/execute.ts +++ b/src/legacy/core_plugins/expressions/public/np_ready/public/execute.ts @@ -61,6 +61,7 @@ export class ExpressionDataHandler { this.promise = interpreter.interpretAst(this.ast, params.context || defaultContext, { getInitialContext, inspectorAdapters: this.inspectorAdapters, + abortSignal: this.abortController.signal, }); } @@ -69,7 +70,18 @@ export class ExpressionDataHandler { }; getData = async () => { - return await this.promise; + try { + return await this.promise; + } catch (e) { + return { + type: 'error', + error: { + type: e.type, + message: e.message, + stack: e.stack, + }, + }; + } }; getExpression = () => { diff --git a/src/legacy/core_plugins/expressions/public/np_ready/public/expression_renderer.test.tsx b/src/legacy/core_plugins/expressions/public/np_ready/public/expression_renderer.test.tsx new file mode 100644 index 0000000000000..26db8753e6403 --- /dev/null +++ b/src/legacy/core_plugins/expressions/public/np_ready/public/expression_renderer.test.tsx @@ -0,0 +1,141 @@ +/* + * 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 { Subject } from 'rxjs'; +import { share } from 'rxjs/operators'; +import { ExpressionRendererImplementation } from './expression_renderer'; +import { ExpressionLoader } from './loader'; +import { mount } from 'enzyme'; +import { EuiProgress } from '@elastic/eui'; + +jest.mock('./loader', () => { + return { + ExpressionLoader: jest.fn().mockImplementation(() => { + return {}; + }), + loader: jest.fn(), + }; +}); + +describe('ExpressionRenderer', () => { + it('starts to load, resolves, and goes back to loading', () => { + const dataSubject = new Subject(); + const data$ = dataSubject.asObservable().pipe(share()); + const renderSubject = new Subject(); + const render$ = renderSubject.asObservable().pipe(share()); + const loadingSubject = new Subject(); + const loading$ = loadingSubject.asObservable().pipe(share()); + + (ExpressionLoader as jest.Mock).mockImplementation(() => { + return { + render$, + data$, + loading$, + update: jest.fn(), + }; + }); + + const instance = mount(); + + loadingSubject.next(); + instance.update(); + + expect(instance.find(EuiProgress)).toHaveLength(1); + + renderSubject.next(1); + + instance.update(); + + expect(instance.find(EuiProgress)).toHaveLength(0); + + instance.setProps({ expression: 'something new' }); + loadingSubject.next(); + instance.update(); + + expect(instance.find(EuiProgress)).toHaveLength(1); + + renderSubject.next(1); + instance.update(); + + expect(instance.find(EuiProgress)).toHaveLength(0); + }); + + it('should display an error message when the expression fails', () => { + const dataSubject = new Subject(); + const data$ = dataSubject.asObservable().pipe(share()); + const renderSubject = new Subject(); + const render$ = renderSubject.asObservable().pipe(share()); + const loadingSubject = new Subject(); + const loading$ = loadingSubject.asObservable().pipe(share()); + + (ExpressionLoader as jest.Mock).mockImplementation(() => { + return { + render$, + data$, + loading$, + update: jest.fn(), + }; + }); + + const instance = mount(); + + dataSubject.next('good data'); + renderSubject.next({ + type: 'error', + error: { message: 'render error' }, + }); + instance.update(); + + expect(instance.find(EuiProgress)).toHaveLength(0); + expect(instance.find('[data-test-subj="expression-renderer-error"]')).toHaveLength(1); + }); + + it('should display a custom error message if the user provides one', () => { + const dataSubject = new Subject(); + const data$ = dataSubject.asObservable().pipe(share()); + const renderSubject = new Subject(); + const render$ = renderSubject.asObservable().pipe(share()); + const loadingSubject = new Subject(); + const loading$ = loadingSubject.asObservable().pipe(share()); + + (ExpressionLoader as jest.Mock).mockImplementation(() => { + return { + render$, + data$, + loading$, + update: jest.fn(), + }; + }); + + const renderErrorFn = jest.fn().mockReturnValue(null); + + const instance = mount( + + ); + + renderSubject.next({ + type: 'error', + error: { message: 'render error' }, + }); + instance.update(); + + expect(renderErrorFn).toHaveBeenCalledWith('render error'); + }); +}); diff --git a/src/legacy/core_plugins/expressions/public/np_ready/public/expression_renderer.tsx b/src/legacy/core_plugins/expressions/public/np_ready/public/expression_renderer.tsx index 9edb5f098ba2e..8359663610f29 100644 --- a/src/legacy/core_plugins/expressions/public/np_ready/public/expression_renderer.tsx +++ b/src/legacy/core_plugins/expressions/public/np_ready/public/expression_renderer.tsx @@ -17,49 +17,49 @@ * under the License. */ -import { useRef, useEffect } from 'react'; +import { useRef, useEffect, useState } from 'react'; import React from 'react'; -import { ExpressionAST, IExpressionLoaderParams, IInterpreterResult } from './types'; -import { IExpressionLoader, ExpressionLoader } from './loader'; +import classNames from 'classnames'; +import { EuiLoadingChart, EuiProgress } from '@elastic/eui'; +import { ExpressionAST, IExpressionLoaderParams, IInterpreterErrorResult } from './types'; +import { ExpressionLoader } from './loader'; // Accept all options of the runner as props except for the // dom element which is provided by the component itself export interface ExpressionRendererProps extends IExpressionLoaderParams { - className: string; dataAttrs?: string[]; expression: string | ExpressionAST; - /** - * If an element is specified, but the response of the expression run can't be rendered - * because it isn't a valid response or the specified renderer isn't available, - * this callback is called with the given result. - */ - onRenderFailure?: (result: IInterpreterResult) => void; + renderError?: (error?: string | null) => React.ReactElement | React.ReactElement[]; +} + +interface State { + isEmpty: boolean; + isLoading: boolean; + error: null | Error; } export type ExpressionRenderer = React.FC; -export const createRenderer = (loader: IExpressionLoader): ExpressionRenderer => ({ - className, +const defaultState: State = { + isEmpty: true, + isLoading: false, + error: null, +}; + +export const ExpressionRendererImplementation = ({ dataAttrs, expression, - onRenderFailure, + renderError, ...options }: ExpressionRendererProps) => { const mountpoint: React.MutableRefObject = useRef(null); const handlerRef: React.MutableRefObject = useRef(null); + const [state, setState] = useState({ ...defaultState }); + // Re-fetch data automatically when the inputs change useEffect(() => { - if (mountpoint.current) { - if (!handlerRef.current) { - handlerRef.current = loader(mountpoint.current, expression, options); - } else { - handlerRef.current.update(expression, options); - } - handlerRef.current.data$.toPromise().catch(result => { - if (onRenderFailure) { - onRenderFailure(result); - } - }); + if (handlerRef.current) { + handlerRef.current.update(expression, options); } }, [ expression, @@ -67,8 +67,66 @@ export const createRenderer = (loader: IExpressionLoader): ExpressionRenderer => options.context, options.variables, options.disableCaching, - mountpoint.current, ]); - return
; + // Initialize the loader only once + useEffect(() => { + if (mountpoint.current && !handlerRef.current) { + handlerRef.current = new ExpressionLoader(mountpoint.current, expression, options); + + handlerRef.current.loading$.subscribe(() => { + if (!handlerRef.current) { + return; + } + setState(prevState => ({ ...prevState, isLoading: true })); + }); + handlerRef.current.render$.subscribe((item: number | IInterpreterErrorResult) => { + if (!handlerRef.current) { + return; + } + if (typeof item !== 'number') { + setState(() => ({ + ...defaultState, + isEmpty: false, + error: item.error, + })); + } else { + setState(() => ({ + ...defaultState, + isEmpty: false, + })); + } + }); + } + }, [mountpoint.current]); + + useEffect(() => { + // We only want a clean up to run when the entire component is unloaded, not on every render + return function cleanup() { + if (handlerRef.current) { + handlerRef.current.destroy(); + handlerRef.current = null; + } + }; + }, []); + + const classes = classNames('expExpressionRenderer', { + 'expExpressionRenderer-isEmpty': state.isEmpty, + 'expExpressionRenderer-hasError': !!state.error, + }); + + return ( +
+ {state.isEmpty ? : null} + {state.isLoading ? : null} + {!state.isLoading && state.error ? ( + renderError ? ( + renderError(state.error.message) + ) : ( +
{state.error.message}
+ ) + ) : null} +
+
+ ); }; diff --git a/src/legacy/core_plugins/expressions/public/np_ready/public/index.ts b/src/legacy/core_plugins/expressions/public/np_ready/public/index.ts index 428b431d298ad..2ff71a6df60cf 100644 --- a/src/legacy/core_plugins/expressions/public/np_ready/public/index.ts +++ b/src/legacy/core_plugins/expressions/public/np_ready/public/index.ts @@ -22,7 +22,7 @@ import { ExpressionsPublicPlugin } from './plugin'; export * from './plugin'; export { ExpressionRenderer, ExpressionRendererProps } from './expression_renderer'; -export { IInterpreterRenderFunction } from './types'; +export { IInterpreterRenderFunction, IInterpreterRenderHandlers } from './types'; export function plugin(initializerContext: PluginInitializerContext) { return new ExpressionsPublicPlugin(initializerContext); diff --git a/src/legacy/core_plugins/expressions/public/np_ready/public/loader.test.ts b/src/legacy/core_plugins/expressions/public/np_ready/public/loader.test.ts index df695c039e02e..36917bf4e8384 100644 --- a/src/legacy/core_plugins/expressions/public/np_ready/public/loader.test.ts +++ b/src/legacy/core_plugins/expressions/public/np_ready/public/loader.test.ts @@ -19,6 +19,7 @@ import { first } from 'rxjs/operators'; import { loader, ExpressionLoader } from './loader'; +import { ExpressionDataHandler } from './execute'; import { fromExpression } from '@kbn/interpreter/common'; import { IInterpreterRenderHandlers } from './types'; import { Observable } from 'rxjs'; @@ -48,27 +49,37 @@ jest.mock('./services', () => { }; }); +jest.mock('./execute', () => { + const actual = jest.requireActual('./execute'); + return { + ExpressionDataHandler: jest + .fn() + .mockImplementation((...args) => new actual.ExpressionDataHandler(...args)), + execute: jest.fn().mockReturnValue(actual.execute), + }; +}); + describe('execute helper function', () => { - it('returns ExpressionDataHandler instance', () => { + it('returns ExpressionLoader instance', () => { const response = loader(element, '', {}); expect(response).toBeInstanceOf(ExpressionLoader); }); }); -describe('ExpressionDataHandler', () => { +describe('ExpressionLoader', () => { const expressionString = ''; describe('constructor', () => { it('accepts expression string', () => { - const expressionDataHandler = new ExpressionLoader(element, expressionString, {}); - expect(expressionDataHandler.getExpression()).toEqual(expressionString); + const expressionLoader = new ExpressionLoader(element, expressionString, {}); + expect(expressionLoader.getExpression()).toEqual(expressionString); }); it('accepts expression AST', () => { const expressionAST = fromExpression(expressionString) as ExpressionAST; - const expressionDataHandler = new ExpressionLoader(element, expressionAST, {}); - expect(expressionDataHandler.getExpression()).toEqual(expressionString); - expect(expressionDataHandler.getAst()).toEqual(expressionAST); + const expressionLoader = new ExpressionLoader(element, expressionAST, {}); + expect(expressionLoader.getExpression()).toEqual(expressionString); + expect(expressionLoader.getAst()).toEqual(expressionAST); }); it('creates observables', () => { @@ -117,9 +128,86 @@ describe('ExpressionDataHandler', () => { expect(response).toBe(2); }); - it('cancel() aborts request', () => { - const expressionDataHandler = new ExpressionLoader(element, expressionString, {}); - expressionDataHandler.cancel(); + it('cancels the previous request when the expression is updated', () => { + const cancelMock = jest.fn(); + + (ExpressionDataHandler as jest.Mock).mockImplementationOnce(() => ({ + getData: () => true, + cancel: cancelMock, + })); + + const expressionLoader = new ExpressionLoader(element, expressionString, {}); + expressionLoader.update('new', {}); + + expect(cancelMock).toHaveBeenCalledTimes(1); + }); + + it('does not send an observable message if a request was aborted', () => { + const cancelMock = jest.fn(); + + const getData = jest + .fn() + .mockResolvedValueOnce({ + type: 'error', + error: { + name: 'AbortError', + }, + }) + .mockResolvedValueOnce({ + type: 'real', + }); + + (ExpressionDataHandler as jest.Mock).mockImplementationOnce(() => ({ + getData, + cancel: cancelMock, + })); + (ExpressionDataHandler as jest.Mock).mockImplementationOnce(() => ({ + getData, + cancel: cancelMock, + })); + + const expressionLoader = new ExpressionLoader(element, expressionString, {}); + + expect.assertions(2); + expressionLoader.data$.subscribe({ + next(data) { + expect(data).toEqual({ + type: 'real', + }); + }, + error() { + expect(false).toEqual('Should not be called'); + }, + }); + + expressionLoader.update('new expression', {}); + + expect(getData).toHaveBeenCalledTimes(2); + }); + + it('sends an observable error if the data fetching failed', () => { + const cancelMock = jest.fn(); + + const getData = jest.fn().mockResolvedValue('rejected'); + + (ExpressionDataHandler as jest.Mock).mockImplementationOnce(() => ({ + getData, + cancel: cancelMock, + })); + + const expressionLoader = new ExpressionLoader(element, expressionString, {}); + + expect.assertions(2); + expressionLoader.data$.subscribe({ + next(data) { + expect(data).toEqual('Should not be called'); + }, + error(error) { + expect(error.message).toEqual('Could not fetch data'); + }, + }); + + expect(getData).toHaveBeenCalledTimes(1); }); it('inspect() returns correct inspector adapters', () => { diff --git a/src/legacy/core_plugins/expressions/public/np_ready/public/loader.ts b/src/legacy/core_plugins/expressions/public/np_ready/public/loader.ts index b07b0048bfd52..709fbc78a9b52 100644 --- a/src/legacy/core_plugins/expressions/public/np_ready/public/loader.ts +++ b/src/legacy/core_plugins/expressions/public/np_ready/public/loader.ts @@ -18,11 +18,11 @@ */ import { Observable, Subject } from 'rxjs'; -import { first, share } from 'rxjs/operators'; +import { share } from 'rxjs/operators'; import { Adapters, InspectorSession } from '../../../../../../plugins/inspector/public'; -import { execute, ExpressionDataHandler } from './execute'; +import { ExpressionDataHandler } from './execute'; import { ExpressionRenderHandler } from './render'; -import { RenderId, Data, IExpressionLoaderParams, ExpressionAST } from './types'; +import { Data, IExpressionLoaderParams, ExpressionAST } from './types'; import { getInspector } from './services'; export class ExpressionLoader { @@ -32,12 +32,12 @@ export class ExpressionLoader { events$: ExpressionRenderHandler['events$']; loading$: Observable; - private dataHandler!: ExpressionDataHandler; + private dataHandler: ExpressionDataHandler | undefined; private renderHandler: ExpressionRenderHandler; private dataSubject: Subject; private loadingSubject: Subject; private data: Data; - private params: IExpressionLoaderParams; + private params: IExpressionLoaderParams = {}; constructor( element: HTMLElement, @@ -63,76 +63,108 @@ export class ExpressionLoader { this.render(data); }); - this.params = { - searchContext: { type: 'kibana_context' }, - extraHandlers: params.extraHandlers, - }; + this.setParams(params); - this.execute(expression, params); + this.loadData(expression, this.params); } - destroy() {} + destroy() { + this.dataSubject.complete(); + this.loadingSubject.complete(); + this.renderHandler.destroy(); + if (this.dataHandler) { + this.dataHandler.cancel(); + } + } cancel() { - this.dataHandler.cancel(); + if (this.dataHandler) { + this.dataHandler.cancel(); + } } - getExpression(): string { - return this.dataHandler.getExpression(); + getExpression(): string | undefined { + if (this.dataHandler) { + return this.dataHandler.getExpression(); + } } - getAst(): ExpressionAST { - return this.dataHandler.getAst(); + getAst(): ExpressionAST | undefined { + if (this.dataHandler) { + return this.dataHandler.getAst(); + } } getElement(): HTMLElement { return this.renderHandler.getElement(); } - openInspector(title: string): InspectorSession { - return getInspector().open(this.inspect(), { - title, - }); + openInspector(title: string): InspectorSession | undefined { + const inspector = this.inspect(); + if (inspector) { + return getInspector().open(inspector, { + title, + }); + } } - inspect(): Adapters { - return this.dataHandler.inspect(); + inspect(): Adapters | undefined { + if (this.dataHandler) { + return this.dataHandler.inspect(); + } } - update(expression?: string | ExpressionAST, params?: IExpressionLoaderParams): Promise { - const promise = this.render$.pipe(first()).toPromise(); - if (params && params.searchContext && this.params.searchContext) { - this.params.searchContext = _.defaults( - {}, - params.searchContext, - this.params.searchContext - ) as any; - } + update(expression?: string | ExpressionAST, params?: IExpressionLoaderParams): void { + this.setParams(params); - this.loadingSubject.next(); if (expression) { - this.execute(expression, this.params); + this.loadData(expression, this.params); } else { this.render(this.data); } - return promise; } - private execute = async ( + private loadData = async ( expression: string | ExpressionAST, - params?: IExpressionLoaderParams - ): Promise => { + params: IExpressionLoaderParams + ): Promise => { + this.loadingSubject.next(); if (this.dataHandler) { this.dataHandler.cancel(); } - this.dataHandler = execute(expression, params); - this.data = await this.dataHandler.getData(); - this.dataSubject.next(this.data); - return this.data; + this.setParams(params); + this.dataHandler = new ExpressionDataHandler(expression, params); + const data = await this.dataHandler.getData(); + this.dataSubject.next(data); }; - private async render(data: Data): Promise { - return this.renderHandler.render(data, this.params.extraHandlers); + private render(data: Data): void { + this.loadingSubject.next(); + this.renderHandler.render(data, this.params.extraHandlers); + } + + private setParams(params?: IExpressionLoaderParams) { + if (!params || !Object.keys(params).length) { + return; + } + + if (params.searchContext && this.params.searchContext) { + this.params.searchContext = _.defaults( + {}, + params.searchContext, + this.params.searchContext + ) as any; + } + if (params.extraHandlers && this.params) { + this.params.extraHandlers = params.extraHandlers; + } + + if (!Object.keys(this.params).length) { + this.params = { + ...params, + searchContext: { type: 'kibana_context', ...(params.searchContext || {}) }, + }; + } } } diff --git a/src/legacy/core_plugins/expressions/public/np_ready/public/mocks.ts b/src/legacy/core_plugins/expressions/public/np_ready/public/mocks.tsx similarity index 97% rename from src/legacy/core_plugins/expressions/public/np_ready/public/mocks.ts rename to src/legacy/core_plugins/expressions/public/np_ready/public/mocks.tsx index 6569e8d8d1ec5..1a2f473f4c9a1 100644 --- a/src/legacy/core_plugins/expressions/public/np_ready/public/mocks.ts +++ b/src/legacy/core_plugins/expressions/public/np_ready/public/mocks.tsx @@ -17,6 +17,7 @@ * under the License. */ +import React from 'react'; import { ExpressionsSetup, ExpressionsStart, plugin as pluginInitializer } from '.'; /* eslint-disable */ import { coreMock } from '../../../../../../core/public/mocks'; @@ -44,7 +45,7 @@ const createExpressionsSetupMock = (): ExpressionsSetup => { function createExpressionsStartMock(): ExpressionsStart { return { - ExpressionRenderer: jest.fn(() => null), + ExpressionRenderer: jest.fn(props => <>), execute: jest.fn(), loader: jest.fn(), render: jest.fn(), diff --git a/src/legacy/core_plugins/expressions/public/np_ready/public/plugin.ts b/src/legacy/core_plugins/expressions/public/np_ready/public/plugin.ts index 5a0eecc51ef19..ec70248694990 100644 --- a/src/legacy/core_plugins/expressions/public/np_ready/public/plugin.ts +++ b/src/legacy/core_plugins/expressions/public/np_ready/public/plugin.ts @@ -34,7 +34,7 @@ import { } from '../../../../../../plugins/inspector/public'; import { IInterpreter } from './types'; import { setInterpreter, setInspector, setRenderersRegistry } from './services'; -import { createRenderer } from './expression_renderer'; +import { ExpressionRendererImplementation } from './expression_renderer'; import { ExpressionLoader, loader } from './loader'; import { ExpressionDataHandler, execute } from './execute'; import { ExpressionRenderHandler, render } from './render'; @@ -77,17 +77,16 @@ export class ExpressionsPublicPlugin } public start(core: CoreStart, { inspector }: ExpressionsStartDeps) { - const ExpressionRenderer = createRenderer(loader); setInspector(inspector); return { execute, render, loader, + ExpressionRenderer: ExpressionRendererImplementation, ExpressionDataHandler, ExpressionRenderHandler, ExpressionLoader, - ExpressionRenderer, }; } diff --git a/src/legacy/core_plugins/expressions/public/np_ready/public/render.test.ts b/src/legacy/core_plugins/expressions/public/np_ready/public/render.test.ts index cf606b8fabec2..9d555f9760ee7 100644 --- a/src/legacy/core_plugins/expressions/public/np_ready/public/render.test.ts +++ b/src/legacy/core_plugins/expressions/public/np_ready/public/render.test.ts @@ -20,6 +20,8 @@ import { render, ExpressionRenderHandler } from './render'; import { Observable } from 'rxjs'; import { IInterpreterRenderHandlers } from './types'; +import { getRenderersRegistry } from './services'; +import { first } from 'rxjs/operators'; const element: HTMLElement = {} as HTMLElement; @@ -31,10 +33,11 @@ jest.mock('./services', () => { }, }, }; + return { - getRenderersRegistry: () => ({ - get: (id: string) => renderers[id], - }), + getRenderersRegistry: jest.fn(() => ({ + get: jest.fn((id: string) => renderers[id]), + })), }; }); @@ -46,8 +49,6 @@ describe('render helper function', () => { }); describe('ExpressionRenderHandler', () => { - const data = { type: 'render', as: 'test' }; - it('constructor creates observers', () => { const expressionRenderHandler = new ExpressionRenderHandler(element); expect(expressionRenderHandler.events$).toBeInstanceOf(Observable); @@ -61,27 +62,71 @@ describe('ExpressionRenderHandler', () => { }); describe('render()', () => { - it('throws if invalid data is provided', async () => { + it('sends an observable error and keeps it open if invalid data is provided', async () => { const expressionRenderHandler = new ExpressionRenderHandler(element); - await expect(expressionRenderHandler.render({})).rejects.toThrow(); + const promise1 = expressionRenderHandler.render$.pipe(first()).toPromise(); + expressionRenderHandler.render(false); + await expect(promise1).resolves.toEqual({ + type: 'error', + error: { + message: 'invalid data provided to the expression renderer', + }, + }); + + const promise2 = expressionRenderHandler.render$.pipe(first()).toPromise(); + expressionRenderHandler.render(false); + await expect(promise2).resolves.toEqual({ + type: 'error', + error: { + message: 'invalid data provided to the expression renderer', + }, + }); }); - it('throws if renderer does not exist', async () => { + it('sends an observable error if renderer does not exist', async () => { const expressionRenderHandler = new ExpressionRenderHandler(element); - await expect( - expressionRenderHandler.render({ type: 'render', as: 'something' }) - ).rejects.toThrow(); + const promise = expressionRenderHandler.render$.pipe(first()).toPromise(); + expressionRenderHandler.render({ type: 'render', as: 'something' }); + await expect(promise).resolves.toEqual({ + type: 'error', + error: { + message: `invalid renderer id 'something'`, + }, + }); }); - it('returns a promise', () => { + it('sends an observable error if the rendering function throws', async () => { + (getRenderersRegistry as jest.Mock).mockReturnValueOnce({ get: () => true }); + (getRenderersRegistry as jest.Mock).mockReturnValueOnce({ + get: () => ({ + render: () => { + throw new Error('renderer error'); + }, + }), + }); + const expressionRenderHandler = new ExpressionRenderHandler(element); - expect(expressionRenderHandler.render(data)).toBeInstanceOf(Promise); + const promise = expressionRenderHandler.render$.pipe(first()).toPromise(); + expressionRenderHandler.render({ type: 'render', as: 'something' }); + await expect(promise).resolves.toEqual({ + type: 'error', + error: { + message: 'renderer error', + }, + }); }); - it('resolves a promise once rendering is complete', async () => { + it('sends a next observable once rendering is complete', () => { const expressionRenderHandler = new ExpressionRenderHandler(element); - const response = await expressionRenderHandler.render(data); - expect(response).toBe(1); + expect.assertions(1); + return new Promise(resolve => { + expressionRenderHandler.render$.subscribe(renderCount => { + expect(renderCount).toBe(1); + resolve(); + }); + + expressionRenderHandler.render({ type: 'render', as: 'test' }); + }); }); }); }); diff --git a/src/legacy/core_plugins/expressions/public/np_ready/public/render.ts b/src/legacy/core_plugins/expressions/public/np_ready/public/render.ts index 96f56a4b36202..f67b4c2d8e272 100644 --- a/src/legacy/core_plugins/expressions/public/np_ready/public/render.ts +++ b/src/legacy/core_plugins/expressions/public/np_ready/public/render.ts @@ -19,7 +19,7 @@ import { Observable } from 'rxjs'; import * as Rx from 'rxjs'; -import { share, first } from 'rxjs/operators'; +import { share } from 'rxjs/operators'; import { event, RenderId, Data, IInterpreterRenderHandlers } from './types'; import { getRenderersRegistry } from './services'; @@ -33,19 +33,22 @@ export class ExpressionRenderHandler { private element: HTMLElement; private destroyFn?: any; private renderCount: number = 0; + private renderSubject: Rx.Subject; + private eventsSubject: Rx.Subject; + private updateSubject: Rx.Subject; private handlers: IInterpreterRenderHandlers; constructor(element: HTMLElement) { this.element = element; - const eventsSubject = new Rx.Subject(); - this.events$ = eventsSubject.asObservable().pipe(share()); + this.eventsSubject = new Rx.Subject(); + this.events$ = this.eventsSubject.asObservable().pipe(share()); - const renderSubject = new Rx.Subject(); - this.render$ = renderSubject.asObservable().pipe(share()); + this.renderSubject = new Rx.Subject(); + this.render$ = this.renderSubject.asObservable().pipe(share()); - const updateSubject = new Rx.Subject(); - this.update$ = updateSubject.asObservable().pipe(share()); + this.updateSubject = new Rx.Subject(); + this.update$ = this.updateSubject.asObservable().pipe(share()); this.handlers = { onDestroy: (fn: any) => { @@ -53,39 +56,68 @@ export class ExpressionRenderHandler { }, done: () => { this.renderCount++; - renderSubject.next(this.renderCount); + this.renderSubject.next(this.renderCount); }, reload: () => { - updateSubject.next(null); + this.updateSubject.next(null); }, update: params => { - updateSubject.next(params); + this.updateSubject.next(params); }, event: data => { - eventsSubject.next(data); + this.eventsSubject.next(data); }, }; } - render = async (data: Data, extraHandlers: IExpressionRendererExtraHandlers = {}) => { - if (!data || data.type !== 'render' || !data.as) { - throw new Error('invalid data provided to expression renderer'); + render = (data: Data, extraHandlers: IExpressionRendererExtraHandlers = {}) => { + if (!data || typeof data !== 'object') { + this.renderSubject.next({ + type: 'error', + error: { + message: 'invalid data provided to the expression renderer', + }, + }); + return; } - if (!getRenderersRegistry().get(data.as)) { - throw new Error(`invalid renderer id '${data.as}'`); + if (data.type !== 'render' || !data.as) { + if (data.type === 'error') { + this.renderSubject.next(data); + } else { + this.renderSubject.next({ + type: 'error', + error: { message: 'invalid data provided to the expression renderer' }, + }); + } + return; } - const promise = this.render$.pipe(first()).toPromise(); - - getRenderersRegistry() - .get(data.as) - .render(this.element, data.value, { ...this.handlers, ...extraHandlers }); + if (!getRenderersRegistry().get(data.as)) { + this.renderSubject.next({ + type: 'error', + error: { message: `invalid renderer id '${data.as}'` }, + }); + return; + } - return promise; + try { + // Rendering is asynchronous, completed by handlers.done() + getRenderersRegistry() + .get(data.as) + .render(this.element, data.value, { ...this.handlers, ...extraHandlers }); + } catch (e) { + this.renderSubject.next({ + type: 'error', + error: { type: e.type, message: e.message }, + }); + } }; destroy = () => { + this.renderSubject.complete(); + this.eventsSubject.complete(); + this.updateSubject.complete(); if (this.destroyFn) { this.destroyFn(); } diff --git a/src/legacy/core_plugins/expressions/public/np_ready/public/services.ts b/src/legacy/core_plugins/expressions/public/np_ready/public/services.ts index 4d95a8a91d0bb..ae2a7955233d1 100644 --- a/src/legacy/core_plugins/expressions/public/np_ready/public/services.ts +++ b/src/legacy/core_plugins/expressions/public/np_ready/public/services.ts @@ -17,25 +17,11 @@ * under the License. */ +import { createGetterSetter } from '../../../../../../plugins/kibana_utils/public'; import { IInterpreter } from './types'; import { Start as IInspector } from '../../../../../../plugins/inspector/public'; import { ExpressionsSetup } from './plugin'; -const createGetterSetter = (name: string): [() => T, (value: T) => void] => { - let value: T; - - const get = (): T => { - if (!value) throw new Error(`${name} was not set`); - return value; - }; - - const set = (newValue: T) => { - value = newValue; - }; - - return [get, set]; -}; - export const [getInspector, setInspector] = createGetterSetter('Inspector'); export const [getInterpreter, setInterpreter] = createGetterSetter('Interpreter'); export const [getRenderersRegistry, setRenderersRegistry] = createGetterSetter< diff --git a/src/legacy/core_plugins/expressions/public/np_ready/public/types.ts b/src/legacy/core_plugins/expressions/public/np_ready/public/types.ts index 09aaa363c9492..0390440298c55 100644 --- a/src/legacy/core_plugins/expressions/public/np_ready/public/types.ts +++ b/src/legacy/core_plugins/expressions/public/np_ready/public/types.ts @@ -52,16 +52,25 @@ export interface IExpressionLoaderParams { export interface IInterpreterHandlers { getInitialContext: IGetInitialContext; inspectorAdapters?: Adapters; + abortSignal?: AbortSignal; } -export interface IInterpreterResult { +export interface IInterpreterErrorResult { + type: 'error'; + error: { message: string; name: string; stack: string }; +} + +export interface IInterpreterSuccessResult { type: string; as?: string; value?: unknown; error?: unknown; } +export type IInterpreterResult = IInterpreterSuccessResult & IInterpreterErrorResult; + export interface IInterpreterRenderHandlers { + // Done increments the number of rendering successes done: () => void; onDestroy: (fn: () => void) => void; reload: () => void; diff --git a/src/legacy/core_plugins/kbn_doc_views/public/views/table/table.test.tsx b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table.test.tsx index f7e16ead932fa..e341b7c4a5a86 100644 --- a/src/legacy/core_plugins/kbn_doc_views/public/views/table/table.test.tsx +++ b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table.test.tsx @@ -65,7 +65,7 @@ const indexPattern = { }, metaFields: ['_index', '_score'], flattenHit: undefined, - formatHit: jest.fn(hit => hit), + formatHit: jest.fn(hit => hit._source), } as IndexPattern; indexPattern.flattenHit = flattenHitWrapper(indexPattern, indexPattern.metaFields); diff --git a/src/legacy/core_plugins/kbn_doc_views/public/views/table/table.tsx b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table.tsx index 8309eaa403f4c..7158739e5d437 100644 --- a/src/legacy/core_plugins/kbn_doc_views/public/views/table/table.tsx +++ b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table.tsx @@ -19,7 +19,7 @@ import React, { useState } from 'react'; import { DocViewRenderProps } from 'ui/registry/doc_views'; import { DocViewTableRow } from './table_row'; -import { formatValue, arrayContainsObjects } from './table_helper'; +import { arrayContainsObjects, trimAngularSpan } from './table_helper'; const COLLAPSE_LINE_LENGTH = 350; @@ -48,8 +48,9 @@ export function DocViewTable({ .sort() .map(field => { const valueRaw = flattened[field]; - const value = formatValue(valueRaw, formatted[field]); - const isCollapsible = typeof value === 'string' && value.length > COLLAPSE_LINE_LENGTH; + const value = trimAngularSpan(String(formatted[field])); + + const isCollapsible = value.length > COLLAPSE_LINE_LENGTH; const isCollapsed = isCollapsible && !fieldRowOpen[field]; const toggleColumn = onRemoveColumn && onAddColumn && Array.isArray(columns) diff --git a/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_helper.test.ts b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_helper.test.ts index f075e06c7651f..2402d4dddb874 100644 --- a/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_helper.test.ts +++ b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_helper.test.ts @@ -16,91 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { - replaceMarkWithReactDom, - convertAngularHtml, - arrayContainsObjects, - formatValue, -} from './table_helper'; - -describe('replaceMarkWithReactDom', () => { - it(`converts test to react nodes`, () => { - const actual = replaceMarkWithReactDom( - 'marked1 blablabla marked2 end' - ); - expect(actual).toMatchInlineSnapshot(` - - - - - marked1 - - blablabla - - - - marked2 - - end - - - `); - }); - - it(`doesn't convert invalid markup to react dom nodes`, () => { - const actual = replaceMarkWithReactDom('test sdf sdf'); - expect(actual).toMatchInlineSnapshot(` - - - test sdf - - - sdf - - - - - `); - }); - - it(`returns strings without markup unchanged `, () => { - const actual = replaceMarkWithReactDom('blablabla'); - expect(actual).toMatchInlineSnapshot(` - - blablabla - - `); - }); -}); - -describe('convertAngularHtml', () => { - it(`converts html for usage in angular to usage in react`, () => { - const actual = convertAngularHtml('Good morning!'); - expect(actual).toMatchInlineSnapshot(`"Good morning!"`); - }); - it(`converts html containing for usage in react`, () => { - const actual = convertAngularHtml( - 'Good morningdear reviewer!' - ); - expect(actual).toMatchInlineSnapshot(` - - Good - - - morning - - dear - - - - reviewer - - ! - - - `); - }); -}); +import { arrayContainsObjects } from './table_helper'; describe('arrayContainsObjects', () => { it(`returns false for an array of primitives`, () => { @@ -128,50 +44,3 @@ describe('arrayContainsObjects', () => { expect(actual).toBeFalsy(); }); }); - -describe('formatValue', () => { - it(`formats an array of objects`, () => { - const actual = formatValue([{ test: '123' }, ''], ''); - expect(actual).toMatchInlineSnapshot(` - "{ - \\"test\\": \\"123\\" - } - \\"\\"" - `); - }); - it(`formats an array of primitives`, () => { - const actual = formatValue(['test1', 'test2'], ''); - expect(actual).toMatchInlineSnapshot(`"test1, test2"`); - }); - it(`formats an object`, () => { - const actual = formatValue({ test: 1 }, ''); - expect(actual).toMatchInlineSnapshot(` - "{ - \\"test\\": 1 - }" - `); - }); - it(`formats an angular formatted string `, () => { - const actual = formatValue( - '', - 'Good morningdear reviewer!' - ); - expect(actual).toMatchInlineSnapshot(` - - Good - - - morning - - dear - - - - reviewer - - ! - - - `); - }); -}); diff --git a/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_helper.tsx b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_helper.tsx index e959ec336bf3a..8835e95022d7c 100644 --- a/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_helper.tsx +++ b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_helper.tsx @@ -16,70 +16,17 @@ * specific language governing permissions and limitations * under the License. */ -import React from 'react'; -import { unescape } from 'lodash'; -/** - * Convert markup of the given string to ReactNodes - * @param text - */ -export function replaceMarkWithReactDom(text: string): React.ReactNode { - return ( - <> - {text.split('').map((markedText, idx) => { - const sub = markedText.split(''); - if (sub.length === 1) { - return markedText; - } - return ( - - {sub[0]} - {sub[1]} - - ); - })} - - ); -} - -/** - * Current html of the formatter is angular flavored, this current workaround - * should be removed when all consumers of the formatHit function are react based - */ -export function convertAngularHtml(html: string): string | React.ReactNode { - if (typeof html === 'string') { - const cleaned = html.replace('', '').replace('', ''); - const unescaped = unescape(cleaned); - if (unescaped.indexOf('') !== -1) { - return replaceMarkWithReactDom(unescaped); - } - return unescaped; - } - return html; -} /** * Returns true if the given array contains at least 1 object */ -export function arrayContainsObjects(value: unknown[]) { +export function arrayContainsObjects(value: unknown[]): boolean { return Array.isArray(value) && value.some(v => typeof v === 'object' && v !== null); } /** - * The current field formatter provides html for angular usage - * This html is cleaned up and prepared for usage in the react world - * Furthermore test are converted to ReactNodes + * Removes markup added by kibana fields html formatter */ -export function formatValue( - value: null | string | number | boolean | object | Array, - valueFormatted: string -): string | React.ReactNode { - if (Array.isArray(value) && arrayContainsObjects(value)) { - return value.map(v => JSON.stringify(v, null, 2)).join('\n'); - } else if (Array.isArray(value)) { - return value.join(', '); - } else if (typeof value === 'object' && value !== null) { - return JSON.stringify(value, null, 2); - } else { - return typeof valueFormatted === 'string' ? convertAngularHtml(valueFormatted) : String(value); - } +export function trimAngularSpan(text: string): string { + return text.replace(/^/, '').replace(/<\/span>$/, ''); } diff --git a/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_row.tsx b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_row.tsx index 2059e35b2c42e..1d979f82d39d8 100644 --- a/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_row.tsx +++ b/src/legacy/core_plugins/kbn_doc_views/public/views/table/table_row.tsx @@ -85,7 +85,7 @@ export function DocViewTableRow({ )} - + {isCollapsible && ( @@ -93,9 +93,15 @@ export function DocViewTableRow({ )} {displayUnderscoreWarning && } {displayNoMappingWarning && } -
- {value} -
+
); diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/validation_wrapper.tsx b/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/validation_wrapper.tsx index a3a41d2249b32..1dd1ab49d9a47 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/validation_wrapper.tsx +++ b/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/validation_wrapper.tsx @@ -29,7 +29,7 @@ interface ValidationWrapperProps extends VisOptionsProps { } interface Item { - valid: boolean; + isValid: boolean; } function ValidationWrapper({ @@ -37,20 +37,17 @@ function ValidationWrapper({ ...rest }: ValidationWrapperProps) { const [panelState, setPanelState] = useState({} as { [key: string]: Item }); - const isPanelValid = Object.values(panelState).every(item => item.valid); + const isPanelValid = Object.values(panelState).every(item => item.isValid); const { setValidity } = rest; - const setValidityHandler = useCallback( - (paramName: string, isValid: boolean) => { - setPanelState({ - ...panelState, - [paramName]: { - valid: isValid, - }, - }); - }, - [panelState] - ); + const setValidityHandler = useCallback((paramName: string, isValid: boolean) => { + setPanelState(state => ({ + ...state, + [paramName]: { + isValid, + }, + })); + }, []); useEffect(() => { setValidity(isPanelValid); diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/gauge/ranges_panel.tsx b/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/gauge/ranges_panel.tsx index c1fa3475470f1..1045543512c6b 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/gauge/ranges_panel.tsx +++ b/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/gauge/ranges_panel.tsx @@ -17,14 +17,16 @@ * under the License. */ -import React from 'react'; +import React, { useCallback } from 'react'; import { EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import { ColorSchemas } from 'ui/vislib/components/color/colormaps'; import { ColorRanges, ColorSchemaOptions, SwitchOption } from '../../common'; -import { SetColorSchemaOptionsValue } from '../../common/color_schema'; import { GaugeOptionsInternalProps } from '.'; +import { ColorSchemaVislibParams } from '../../../types'; +import { Gauge } from '../../../gauge'; function RangesPanel({ setGaugeValue, @@ -35,6 +37,22 @@ function RangesPanel({ uiState, vis, }: GaugeOptionsInternalProps) { + const setColorSchemaOptions = useCallback( + (paramName: T, value: ColorSchemaVislibParams[T]) => { + setGaugeValue(paramName, value as Gauge[T]); + // set outline if color schema is changed to greys + // if outline wasn't set explicitly yet + if ( + paramName === 'colorSchema' && + (value as string) === ColorSchemas.Greys && + typeof stateParams.gauge.outline === 'undefined' + ) { + setGaugeValue('outline', true); + } + }, + [setGaugeValue, stateParams] + ); + return ( @@ -84,7 +102,16 @@ function RangesPanel({ colorSchemas={vis.type.editorConfig.collections.colorSchemas} invertColors={stateParams.gauge.invertColors} uiState={uiState} - setValue={setGaugeValue as SetColorSchemaOptionsValue} + setValue={setColorSchemaOptions} + /> + + { describe('boundsMargin', () => { it('should set validity as true when value is positive', () => { - const comp = shallow(); - comp.find({ paramName: BOUNDS_MARGIN }).prop('setValue')(BOUNDS_MARGIN, 5); + defaultProps.axis.scale.boundsMargin = 5; + mount(); expect(setMultipleValidity).toBeCalledWith(BOUNDS_MARGIN, true); }); it('should set validity as true when value is empty', () => { - const comp = shallow(); - comp.find({ paramName: BOUNDS_MARGIN }).prop('setValue')(BOUNDS_MARGIN, ''); + const comp = mount(); + comp.setProps({ + axis: { ...valueAxis, scale: { ...valueAxis.scale, boundsMargin: undefined } }, + }); expect(setMultipleValidity).toBeCalledWith(BOUNDS_MARGIN, true); }); it('should set validity as false when value is negative', () => { defaultProps.axis.scale.defaultYExtents = true; - const comp = shallow(); - comp.find({ paramName: BOUNDS_MARGIN }).prop('setValue')(BOUNDS_MARGIN, -1); + const comp = mount(); + comp.setProps({ + axis: { ...valueAxis, scale: { ...valueAxis.scale, boundsMargin: -1 } }, + }); expect(setMultipleValidity).toBeCalledWith(BOUNDS_MARGIN, false); }); @@ -103,7 +107,6 @@ describe('CustomExtentsOptions component', () => { const comp = shallow(); comp.find({ paramName: DEFAULT_Y_EXTENTS }).prop('setValue')(DEFAULT_Y_EXTENTS, false); - expect(setMultipleValidity).toBeCalledWith(BOUNDS_MARGIN, true); const newScale = { ...defaultProps.axis.scale, boundsMargin: undefined, diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/custom_extents_options.tsx b/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/custom_extents_options.tsx index e04d8e646160e..df7eedd2c0ea1 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/custom_extents_options.tsx +++ b/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/custom_extents_options.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React, { useState, useCallback } from 'react'; +import React, { useCallback, useEffect } from 'react'; import { i18n } from '@kbn/i18n'; import { ValueAxis } from '../../../types'; @@ -38,21 +38,18 @@ function CustomExtentsOptions({ setValueAxis, setValueAxisScale, }: CustomExtentsOptionsProps) { - const [isBoundsMarginValid, setIsBoundsMarginValid] = useState(true); const invalidBoundsMarginMessage = i18n.translate( 'kbnVislibVisTypes.controls.pointSeries.valueAxes.scaleToDataBounds.minNeededBoundsMargin', { defaultMessage: 'Bounds margin must be greater than or equal to 0.' } ); - const setBoundsMargin = useCallback( - (paramName: 'boundsMargin', value: number | '') => { - const isValid = value === '' ? true : value >= 0; - setIsBoundsMarginValid(isValid); - setMultipleValidity('boundsMargin', isValid); + const isBoundsMarginValid = + !axis.scale.defaultYExtents || !axis.scale.boundsMargin || axis.scale.boundsMargin >= 0; - setValueAxisScale(paramName, value); - }, - [setMultipleValidity, setValueAxisScale] + const setBoundsMargin = useCallback( + (paramName: 'boundsMargin', value: number | '') => + setValueAxisScale(paramName, value === '' ? undefined : value), + [setValueAxisScale] ); const onDefaultYExtentsChange = useCallback( @@ -60,7 +57,6 @@ function CustomExtentsOptions({ const scale = { ...axis.scale, [paramName]: value }; if (!scale.defaultYExtents) { delete scale.boundsMargin; - setMultipleValidity('boundsMargin', true); } setValueAxis('scale', scale); }, @@ -79,6 +75,12 @@ function CustomExtentsOptions({ [setValueAxis, axis.scale] ); + useEffect(() => { + setMultipleValidity('boundsMargin', isBoundsMarginValid); + + return () => setMultipleValidity('boundsMargin', true); + }, [isBoundsMarginValid, setMultipleValidity]); + return ( <> setMultipleValidity('yExtents', true); - }, [isValid]); + }, [isValid, setMultipleValidity]); return ( diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/gauge.d.ts b/src/legacy/core_plugins/kbn_vislib_vis_types/public/gauge.d.ts index bb042abfdcca7..ff8345e9a5b25 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/gauge.d.ts +++ b/src/legacy/core_plugins/kbn_vislib_vis_types/public/gauge.d.ts @@ -32,6 +32,7 @@ interface Gauge extends ColorSchemaVislibParams { gaugeType: GaugeTypes; labels: Labels; percentageMode: boolean; + outline?: boolean; scale: { show: boolean; labels: false; diff --git a/src/legacy/core_plugins/kibana/migrations/migrations.js b/src/legacy/core_plugins/kibana/migrations/migrations.js index 03b2f5b898345..9f3561f39101d 100644 --- a/src/legacy/core_plugins/kibana/migrations/migrations.js +++ b/src/legacy/core_plugins/kibana/migrations/migrations.js @@ -285,6 +285,50 @@ function transformFilterStringToQueryObject(doc) { } return newDoc; } +function transformSplitFiltersStringToQueryObject(doc) { + // Migrate split_filters in TSVB objects that weren't migrated in 7.3 + // If any filters exist and they are a string, we assume them to be lucene syntax and transform the filter into an object accordingly + const newDoc = cloneDeep(doc); + const visStateJSON = get(doc, 'attributes.visState'); + if (visStateJSON) { + let visState; + try { + visState = JSON.parse(visStateJSON); + } catch (e) { + // let it go, the data is invalid and we'll leave it as is + } + if (visState) { + const visType = get(visState, 'params.type'); + const tsvbTypes = ['metric', 'markdown', 'top_n', 'gauge', 'table', 'timeseries']; + if (tsvbTypes.indexOf(visType) === -1) { + // skip + return doc; + } + // migrate the series split_filter filters + const series = get(visState, 'params.series') || []; + series.forEach(item => { + // series item split filters filter + if (item.split_filters) { + const splitFilters = get(item, 'split_filters') || []; + if (splitFilters.length > 0) { + // only transform split_filter filters if we have filters + splitFilters.forEach(filter => { + if (typeof filter.filter === 'string') { + const filterfilterObject = { + query: filter.filter, + language: 'lucene', + }; + filter.filter = filterfilterObject; + } + }); + } + } + }); + newDoc.attributes.visState = JSON.stringify(visState); + } + } + return newDoc; +} function migrateFiltersAggQuery(doc) { const visStateJSON = get(doc, 'attributes.visState'); @@ -435,6 +479,10 @@ const executeSearchMigrations740 = flow( migrateSearchSortToNestedArray, ); +const executeMigrations742 = flow( + transformSplitFiltersStringToQueryObject +); + export const migrations = { 'index-pattern': { '6.5.0': doc => { @@ -541,6 +589,8 @@ export const migrations = { '7.2.0': doc => executeMigrations720(doc), '7.3.0': executeMigrations730, '7.3.1': executeVisualizationMigrations731, + // migrate split_filters that were not migrated in 7.3.0 (transformFilterStringToQueryObject). + '7.4.2': executeMigrations742, }, dashboard: { '7.0.0': doc => { diff --git a/src/legacy/core_plugins/kibana/migrations/migrations.test.js b/src/legacy/core_plugins/kibana/migrations/migrations.test.js index 77a4c7eabe86e..5368169d6dd6d 100644 --- a/src/legacy/core_plugins/kibana/migrations/migrations.test.js +++ b/src/legacy/core_plugins/kibana/migrations/migrations.test.js @@ -1180,6 +1180,123 @@ Array [ expect(migratedDoc).toEqual({ attributes: { visState: JSON.stringify(expected) } }); }); }); + describe('7.4.2 tsvb split_filters migration', () => { + const migrate = doc => migrations.visualization['7.4.2'](doc); + const generateDoc = ({ params }) => ({ + attributes: { + title: 'My Vis', + description: 'This is my super cool vis.', + visState: JSON.stringify({ params }), + uiStateJSON: '{}', + version: 1, + kibanaSavedObjectMeta: { + searchSourceJSON: '{}', + }, + }, + }); + it('should change series item filters from a string into an object for all filters', () => { + const params = { + type: 'timeseries', + filter: { + query: 'bytes:>1000', + language: 'lucene' + }, + series: [ + { + split_filters: [{ filter: 'bytes:>1000' }], + }, + ] + }; + const timeSeriesDoc = generateDoc({ params: params }); + const migratedtimeSeriesDoc = migrate(timeSeriesDoc); + const timeSeriesParams = JSON.parse(migratedtimeSeriesDoc.attributes.visState).params; + expect(Object.keys(timeSeriesParams.filter)).toEqual( + expect.arrayContaining(['query', 'language']) + ); + expect(timeSeriesParams.series[0].split_filters[0].filter).toEqual( + { query: 'bytes:>1000', language: 'lucene' } + ); + }); + it('should change series item split filters when there is no filter item', () => { + const params = { + type: 'timeseries', + filter: { + query: 'bytes:>1000', + language: 'lucene' + }, + series: [ + { + split_filters: [{ filter: 'bytes:>1000' }], + }, + ], + annotations: [ + { + query_string: { + query: 'bytes:>1000', + language: 'lucene' + } + } + ], + }; + const timeSeriesDoc = generateDoc({ params: params }); + const migratedtimeSeriesDoc = migrate(timeSeriesDoc); + const timeSeriesParams = JSON.parse(migratedtimeSeriesDoc.attributes.visState).params; + expect(timeSeriesParams.series[0].split_filters[0].filter).toEqual( + { query: 'bytes:>1000', language: 'lucene' } + ); + }); + it('should not convert split_filters to objects if there are no split filter filters', () => { + const params = { + type: 'timeseries', + filter: { + query: 'bytes:>1000', + language: 'lucene' + }, + series: [ + { + split_filters: [], + }, + ] + }; + const timeSeriesDoc = generateDoc({ params: params }); + const migratedtimeSeriesDoc = migrate(timeSeriesDoc); + const timeSeriesParams = JSON.parse(migratedtimeSeriesDoc.attributes.visState).params; + expect(timeSeriesParams.series[0].split_filters).not.toHaveProperty('query'); + }); + it('should do nothing if a split_filter is already a query:language object', () => { + const params = { + type: 'timeseries', + filter: { + query: 'bytes:>1000', + language: 'lucene' + }, + series: [ + { + split_filters: [{ + filter: { + query: 'bytes:>1000', + language: 'lucene', + } + }], + }, + ], + annotations: [ + { + query_string: { + query: 'bytes:>1000', + language: 'lucene' + } + } + ], + }; + const timeSeriesDoc = generateDoc({ params: params }); + const migratedtimeSeriesDoc = migrate(timeSeriesDoc); + const timeSeriesParams = JSON.parse(migratedtimeSeriesDoc.attributes.visState).params; + expect(timeSeriesParams.series[0].split_filters[0].filter.query).toEqual('bytes:>1000'); + expect(timeSeriesParams.series[0].split_filters[0].filter.language).toEqual('lucene'); + + }); + }); }); describe('dashboard', () => { diff --git a/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/discover_field.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/discover_field.js index 5e8cfc8e1609c..9ac76bfcfe04e 100644 --- a/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/discover_field.js +++ b/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/discover_field.js @@ -23,7 +23,6 @@ import _ from 'lodash'; import sinon from 'sinon'; import ngMock from 'ng_mock'; import expect from '@kbn/expect'; -import 'ui/private'; import '../../components/field_chooser/discover_field'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; diff --git a/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_calculator.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_calculator.js index 3130ac29eb84d..3ddee3495f36d 100644 --- a/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_calculator.js +++ b/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_calculator.js @@ -22,7 +22,6 @@ import _ from 'lodash'; import ngMock from 'ng_mock'; import { fieldCalculator } from '../../components/field_chooser/lib/field_calculator'; import expect from '@kbn/expect'; -import 'ui/private'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; // Load the kibana app dependencies. diff --git a/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_chooser.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_chooser.js index c2be750ec7f63..a5b55e50eb90e 100644 --- a/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_chooser.js +++ b/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/field_chooser.js @@ -23,7 +23,6 @@ import _ from 'lodash'; import sinon from 'sinon'; import expect from '@kbn/expect'; import $ from 'jquery'; -import 'ui/private'; import '../../components/field_chooser/field_chooser'; import FixturesHitsProvider from 'fixtures/hits'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; diff --git a/src/legacy/core_plugins/kibana/public/discover/_index.scss b/src/legacy/core_plugins/kibana/public/discover/_index.scss index 0b0bd12cb268b..b311dd8a34778 100644 --- a/src/legacy/core_plugins/kibana/public/discover/_index.scss +++ b/src/legacy/core_plugins/kibana/public/discover/_index.scss @@ -11,7 +11,7 @@ @import 'components/fetch_error/index'; @import 'components/field_chooser/index'; @import 'angular/directives/index'; -@import 'doc_table/index'; +@import 'angular/doc_table/index'; @import 'hacks'; @@ -23,4 +23,4 @@ @import 'doc_viewer/index'; // Context styles -@import 'context/index'; +@import 'angular/context/index'; diff --git a/src/legacy/core_plugins/kibana/public/discover/context/index.html b/src/legacy/core_plugins/kibana/public/discover/angular/context.html similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/index.html rename to src/legacy/core_plugins/kibana/public/discover/angular/context.html diff --git a/src/legacy/core_plugins/kibana/public/discover/context/index.js b/src/legacy/core_plugins/kibana/public/discover/angular/context.js similarity index 87% rename from src/legacy/core_plugins/kibana/public/discover/context/index.js rename to src/legacy/core_plugins/kibana/public/discover/angular/context.js index 902bee2badb7c..58d1626ca4b14 100644 --- a/src/legacy/core_plugins/kibana/public/discover/context/index.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context.js @@ -18,16 +18,13 @@ */ import _ from 'lodash'; - -import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter'; -import uiRoutes from 'ui/routes'; import { i18n } from '@kbn/i18n'; +import { getServices, subscribeWithScope } from './../kibana_services'; -import './app'; -import contextAppRouteTemplate from './index.html'; +import './context_app'; +import contextAppRouteTemplate from './context.html'; import { getRootBreadcrumbs } from '../breadcrumbs'; -import { npStart } from 'ui/new_platform'; -import { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; +const { FilterBarQueryFilterProvider, uiRoutes, chrome } = getServices(); const k7Breadcrumbs = $route => { const { indexPattern } = $route.current.locals; @@ -47,12 +44,11 @@ const k7Breadcrumbs = $route => { ]; }; - uiRoutes // deprecated route, kept for compatibility // should be removed in the future .when('/context/:indexPatternId/:type/:id*', { - redirectTo: '/context/:indexPatternId/:id' + redirectTo: '/context/:indexPatternId/:id', }) .when('/context/:indexPatternId/:id*', { controller: ContextAppRouteController, @@ -92,7 +88,7 @@ function ContextAppRouteController($routeParams, $scope, AppState, config, index }); this.anchorId = $routeParams.id; this.indexPattern = indexPattern; - this.discoverUrl = npStart.core.chrome.navLinks.get('kibana:discover').url; + this.discoverUrl = chrome.navLinks.get('kibana:discover').url; this.filters = _.cloneDeep(queryFilter.getFilters()); } diff --git a/src/legacy/core_plugins/kibana/public/discover/context/NOTES.md b/src/legacy/core_plugins/kibana/public/discover/angular/context/NOTES.md similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/NOTES.md rename to src/legacy/core_plugins/kibana/public/discover/angular/context/NOTES.md diff --git a/src/legacy/core_plugins/kibana/public/discover/context/_index.scss b/src/legacy/core_plugins/kibana/public/discover/angular/context/_index.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/_index.scss rename to src/legacy/core_plugins/kibana/public/discover/angular/context/_index.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/context/api/__tests__/_stubs.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/_stubs.js similarity index 98% rename from src/legacy/core_plugins/kibana/public/discover/context/api/__tests__/_stubs.js rename to src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/_stubs.js index ecb22b20e4d86..f472ff9250eb5 100644 --- a/src/legacy/core_plugins/kibana/public/discover/context/api/__tests__/_stubs.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/_stubs.js @@ -19,7 +19,7 @@ import sinon from 'sinon'; import moment from 'moment'; -import { SearchSource } from 'ui/courier'; +import { SearchSource } from '../../../../kibana_services'; export function createIndexPatternsStub() { return { diff --git a/src/legacy/core_plugins/kibana/public/discover/context/api/__tests__/anchor.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/anchor.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/api/__tests__/anchor.js rename to src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/anchor.js diff --git a/src/legacy/core_plugins/kibana/public/discover/context/api/__tests__/predecessors.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/predecessors.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/api/__tests__/predecessors.js rename to src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/predecessors.js diff --git a/src/legacy/core_plugins/kibana/public/discover/context/api/__tests__/successors.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/successors.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/api/__tests__/successors.js rename to src/legacy/core_plugins/kibana/public/discover/angular/context/api/__tests__/successors.js diff --git a/src/legacy/core_plugins/kibana/public/discover/context/api/anchor.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/anchor.js similarity index 95% rename from src/legacy/core_plugins/kibana/public/discover/context/api/anchor.js rename to src/legacy/core_plugins/kibana/public/discover/angular/context/api/anchor.js index 02a309eaa0165..62bbc6166662f 100644 --- a/src/legacy/core_plugins/kibana/public/discover/context/api/anchor.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/anchor.js @@ -18,11 +18,10 @@ */ import _ from 'lodash'; - import { i18n } from '@kbn/i18n'; +import { getServices } from '../../../kibana_services'; -import { SearchSource } from 'ui/courier'; - +const { SearchSource } = getServices(); export function fetchAnchorProvider(indexPatterns) { return async function fetchAnchor( indexPatternId, diff --git a/src/legacy/core_plugins/kibana/public/discover/context/api/context.ts b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/context.ts similarity index 97% rename from src/legacy/core_plugins/kibana/public/discover/context/api/context.ts rename to src/legacy/core_plugins/kibana/public/discover/angular/context/api/context.ts index 48ac59f1f0855..268f176f2c61e 100644 --- a/src/legacy/core_plugins/kibana/public/discover/context/api/context.ts +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/context.ts @@ -17,10 +17,8 @@ * under the License. */ -// @ts-ignore -import { SearchSource } from 'ui/courier'; import { Filter } from '@kbn/es-query'; -import { IndexPatterns, IndexPattern } from 'ui/index_patterns'; +import { IndexPatterns, IndexPattern, getServices } from '../../../kibana_services'; import { reverseSortDir, SortDirection } from './utils/sorting'; import { extractNanos, convertIsoToMillis } from './utils/date_conversion'; import { fetchHitsInInterval } from './utils/fetch_hits_in_interval'; @@ -36,6 +34,8 @@ export interface EsHitRecord { } export type EsHitRecordList = EsHitRecord[]; +const { SearchSource } = getServices(); + const DAY_MILLIS = 24 * 60 * 60 * 1000; // look from 1 day up to 10000 days into the past and future diff --git a/src/legacy/core_plugins/kibana/public/discover/context/api/utils/__tests__/date_conversion.test.ts b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/__tests__/date_conversion.test.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/api/utils/__tests__/date_conversion.test.ts rename to src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/__tests__/date_conversion.test.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/context/api/utils/__tests__/sorting.test.ts b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/__tests__/sorting.test.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/api/utils/__tests__/sorting.test.ts rename to src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/__tests__/sorting.test.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/context/api/utils/date_conversion.ts b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/date_conversion.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/api/utils/date_conversion.ts rename to src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/date_conversion.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/context/api/utils/fetch_hits_in_interval.ts b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/fetch_hits_in_interval.ts similarity index 97% rename from src/legacy/core_plugins/kibana/public/discover/context/api/utils/fetch_hits_in_interval.ts rename to src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/fetch_hits_in_interval.ts index 9a5436b59714d..2810e5d9d7e66 100644 --- a/src/legacy/core_plugins/kibana/public/discover/context/api/utils/fetch_hits_in_interval.ts +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/fetch_hits_in_interval.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { SearchSource } from 'ui/courier'; +import { SearchSource } from '../../../../kibana_services'; import { convertTimeValueToIso } from './date_conversion'; import { SortDirection } from './sorting'; import { EsHitRecordList } from '../context'; diff --git a/src/legacy/core_plugins/kibana/public/discover/context/api/utils/generate_intervals.ts b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/generate_intervals.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/api/utils/generate_intervals.ts rename to src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/generate_intervals.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/context/api/utils/get_es_query_search_after.ts b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/get_es_query_search_after.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/api/utils/get_es_query_search_after.ts rename to src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/get_es_query_search_after.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/context/api/utils/get_es_query_sort.ts b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/get_es_query_sort.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/api/utils/get_es_query_sort.ts rename to src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/get_es_query_sort.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/context/api/utils/sorting.ts b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/sorting.ts similarity index 96% rename from src/legacy/core_plugins/kibana/public/discover/context/api/utils/sorting.ts rename to src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/sorting.ts index b673270d7a645..4a0f531845f46 100644 --- a/src/legacy/core_plugins/kibana/public/discover/context/api/utils/sorting.ts +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/api/utils/sorting.ts @@ -17,7 +17,7 @@ * under the License. */ -import { IndexPattern } from 'src/legacy/core_plugins/data/public'; +import { IndexPattern } from '../../../../kibana_services'; export enum SortDirection { asc = 'asc', diff --git a/src/legacy/core_plugins/kibana/public/discover/context/components/action_bar/_action_bar.scss b/src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/_action_bar.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/components/action_bar/_action_bar.scss rename to src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/_action_bar.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/context/components/action_bar/_index.scss b/src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/_index.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/components/action_bar/_index.scss rename to src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/_index.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/context/components/action_bar/action_bar.test.tsx b/src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/action_bar.test.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/components/action_bar/action_bar.test.tsx rename to src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/action_bar.test.tsx diff --git a/src/legacy/core_plugins/kibana/public/discover/context/components/action_bar/action_bar.tsx b/src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/action_bar.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/components/action_bar/action_bar.tsx rename to src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/action_bar.tsx diff --git a/src/legacy/core_plugins/kibana/public/discover/context/components/action_bar/action_bar_directive.ts b/src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/action_bar_directive.ts similarity index 89% rename from src/legacy/core_plugins/kibana/public/discover/context/components/action_bar/action_bar_directive.ts rename to src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/action_bar_directive.ts index 0942539e63785..579d9d95c6f71 100644 --- a/src/legacy/core_plugins/kibana/public/discover/context/components/action_bar/action_bar_directive.ts +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/action_bar_directive.ts @@ -16,11 +16,11 @@ * specific language governing permissions and limitations * under the License. */ -// @ts-ignore -import { uiModules } from 'ui/modules'; -import { wrapInI18nContext } from 'ui/i18n'; +import { getServices } from '../../../../kibana_services'; import { ActionBar } from './action_bar'; +const { uiModules, wrapInI18nContext } = getServices(); + uiModules.get('apps/context').directive('contextActionBar', function(reactDirective: any) { return reactDirective(wrapInI18nContext(ActionBar)); }); diff --git a/src/legacy/core_plugins/kibana/public/discover/context/components/action_bar/action_bar_warning.tsx b/src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/action_bar_warning.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/components/action_bar/action_bar_warning.tsx rename to src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/action_bar_warning.tsx diff --git a/src/legacy/core_plugins/kibana/public/discover/context/components/action_bar/index.ts b/src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/index.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/components/action_bar/index.ts rename to src/legacy/core_plugins/kibana/public/discover/angular/context/components/action_bar/index.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/context/query/actions.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/query/actions.js similarity index 97% rename from src/legacy/core_plugins/kibana/public/discover/context/query/actions.js rename to src/legacy/core_plugins/kibana/public/discover/angular/context/query/actions.js index c55dcc374fa5a..b88e54379f448 100644 --- a/src/legacy/core_plugins/kibana/public/discover/context/query/actions.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/query/actions.js @@ -20,13 +20,13 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { toastNotifications } from 'ui/notify'; +import { toastNotifications } from '../../../kibana_services'; import { fetchAnchorProvider } from '../api/anchor'; import { fetchContextProvider } from '../api/context'; import { QueryParameterActionsProvider } from '../query_parameters'; import { FAILURE_REASONS, LOADING_STATUS } from './constants'; -import { MarkdownSimple } from '../../../../../kibana_react/public'; +import { MarkdownSimple } from '../../../../../../kibana_react/public'; export function QueryActionsProvider(Private, Promise) { const fetchAnchor = Private(fetchAnchorProvider); diff --git a/src/legacy/core_plugins/kibana/public/discover/context/query/constants.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/query/constants.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/query/constants.js rename to src/legacy/core_plugins/kibana/public/discover/angular/context/query/constants.js diff --git a/src/legacy/core_plugins/kibana/public/discover/context/query/index.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/query/index.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/query/index.js rename to src/legacy/core_plugins/kibana/public/discover/angular/context/query/index.js diff --git a/src/legacy/core_plugins/kibana/public/discover/context/query/state.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/query/state.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/query/state.js rename to src/legacy/core_plugins/kibana/public/discover/angular/context/query/state.js diff --git a/src/legacy/core_plugins/kibana/public/discover/context/query_parameters/__tests__/_utils.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/_utils.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/query_parameters/__tests__/_utils.js rename to src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/_utils.js diff --git a/src/legacy/core_plugins/kibana/public/discover/context/query_parameters/__tests__/action_add_filter.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_add_filter.js similarity index 94% rename from src/legacy/core_plugins/kibana/public/discover/context/query_parameters/__tests__/action_add_filter.js rename to src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_add_filter.js index 1c96cbeec04a3..b136b03bd500b 100644 --- a/src/legacy/core_plugins/kibana/public/discover/context/query_parameters/__tests__/action_add_filter.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_add_filter.js @@ -20,9 +20,7 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; import sinon from 'sinon'; - -import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter'; - +import { getServices } from '../../../../kibana_services'; import { createStateStub } from './_utils'; import { QueryParameterActionsProvider } from '../actions'; @@ -36,7 +34,7 @@ describe('context app', function () { beforeEach(ngMock.inject(function createPrivateStubs(Private) { filterManagerStub = createQueryFilterStub(); - Private.stub(FilterBarQueryFilterProvider, filterManagerStub); + Private.stub(getServices().FilterBarQueryFilterProvider, filterManagerStub); addFilter = Private(QueryParameterActionsProvider).addFilter; })); diff --git a/src/legacy/core_plugins/kibana/public/discover/context/query_parameters/__tests__/action_set_predecessor_count.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_set_predecessor_count.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/query_parameters/__tests__/action_set_predecessor_count.js rename to src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_set_predecessor_count.js diff --git a/src/legacy/core_plugins/kibana/public/discover/context/query_parameters/__tests__/action_set_query_parameters.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_set_query_parameters.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/query_parameters/__tests__/action_set_query_parameters.js rename to src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_set_query_parameters.js diff --git a/src/legacy/core_plugins/kibana/public/discover/context/query_parameters/__tests__/action_set_successor_count.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_set_successor_count.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/query_parameters/__tests__/action_set_successor_count.js rename to src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/__tests__/action_set_successor_count.js diff --git a/src/legacy/core_plugins/kibana/public/discover/context/query_parameters/actions.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/actions.js similarity index 92% rename from src/legacy/core_plugins/kibana/public/discover/context/query_parameters/actions.js rename to src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/actions.js index 1c895b8d9e1c5..9f7b180e8fe7d 100644 --- a/src/legacy/core_plugins/kibana/public/discover/context/query_parameters/actions.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/actions.js @@ -18,9 +18,8 @@ */ import _ from 'lodash'; +import { getServices, getFilterGenerator } from '../../../kibana_services'; -import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter'; -import { getFilterGenerator } from 'ui/filter_manager'; import { MAX_CONTEXT_SIZE, MIN_CONTEXT_SIZE, @@ -29,7 +28,7 @@ import { export function QueryParameterActionsProvider(indexPatterns, Private) { - const queryFilter = Private(FilterBarQueryFilterProvider); + const queryFilter = Private(getServices().FilterBarQueryFilterProvider); const filterGen = getFilterGenerator(queryFilter); const setPredecessorCount = (state) => (predecessorCount) => ( diff --git a/src/legacy/core_plugins/kibana/public/discover/context/query_parameters/constants.ts b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/constants.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/query_parameters/constants.ts rename to src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/constants.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/context/query_parameters/index.js b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/index.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/query_parameters/index.js rename to src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/index.js diff --git a/src/legacy/core_plugins/kibana/public/discover/context/query_parameters/state.ts b/src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/state.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/query_parameters/state.ts rename to src/legacy/core_plugins/kibana/public/discover/angular/context/query_parameters/state.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/context/app.html b/src/legacy/core_plugins/kibana/public/discover/angular/context_app.html similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/context/app.html rename to src/legacy/core_plugins/kibana/public/discover/angular/context_app.html diff --git a/src/legacy/core_plugins/kibana/public/discover/context/app.js b/src/legacy/core_plugins/kibana/public/discover/angular/context_app.js similarity index 92% rename from src/legacy/core_plugins/kibana/public/discover/context/app.js rename to src/legacy/core_plugins/kibana/public/discover/angular/context_app.js index 7754f743632cb..c9856ad794952 100644 --- a/src/legacy/core_plugins/kibana/public/discover/context/app.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/context_app.js @@ -18,24 +18,23 @@ */ import _ from 'lodash'; - -import { callAfterBindingsWorkaround } from 'ui/compat'; -import { uiModules } from 'ui/modules'; -import contextAppTemplate from './app.html'; -import './components/action_bar'; -import { getFirstSortableField } from './api/utils/sorting'; +import { getServices, callAfterBindingsWorkaround } from './../kibana_services'; +import contextAppTemplate from './context_app.html'; +import './context/components/action_bar'; +import { getFirstSortableField } from './context/api/utils/sorting'; import { createInitialQueryParametersState, QueryParameterActionsProvider, QUERY_PARAMETER_KEYS, -} from './query_parameters'; +} from './context/query_parameters'; import { createInitialLoadingStatusState, FAILURE_REASONS, LOADING_STATUS, QueryActionsProvider, -} from './query'; -import { timefilter } from 'ui/timefilter'; +} from './context/query'; + +const { uiModules, timefilter } = getServices(); // load directives import '../../../../data/public/legacy'; diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/directives/histogram.tsx b/src/legacy/core_plugins/kibana/public/discover/angular/directives/histogram.tsx index 0dca912653f6c..ab336396b5bed 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/directives/histogram.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/angular/directives/histogram.tsx @@ -23,7 +23,6 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import lightEuiTheme from '@elastic/eui/dist/eui_theme_light.json'; import darkEuiTheme from '@elastic/eui/dist/eui_theme_dark.json'; -import { npStart } from 'ui/new_platform'; import { AnnotationDomainTypes, @@ -44,12 +43,9 @@ import { } from '@elastic/charts'; import { i18n } from '@kbn/i18n'; - -import chrome from 'ui/chrome'; -// @ts-ignore: path dynamic for kibana -import { timezoneProvider } from 'ui/vis/lib/timezone'; import { EuiChartThemeType } from '@elastic/eui/src/themes/charts/themes'; import { Subscription } from 'rxjs'; +import { getServices, timezoneProvider } from '../../kibana_services'; export interface DiscoverHistogramProps { chartData: any; @@ -68,12 +64,12 @@ export class DiscoverHistogram extends Component this.setState({ chartsTheme })); } @@ -145,7 +141,7 @@ export class DiscoverHistogram extends Component diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/directives/no_results.js b/src/legacy/core_plugins/kibana/public/discover/angular/directives/no_results.js index 5f6d32681b50e..b5d3e8a5a01ca 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/directives/no_results.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/directives/no_results.js @@ -32,6 +32,7 @@ import { EuiSpacer, EuiText, } from '@elastic/eui'; +import { getServices } from '../../kibana_services'; // eslint-disable-next-line react/prefer-stateless-function export class DiscoverNoResults extends Component { @@ -39,7 +40,6 @@ export class DiscoverNoResults extends Component { shardFailures: PropTypes.array, timeFieldName: PropTypes.string, queryLanguage: PropTypes.string, - getDocLink: PropTypes.func.isRequired, }; render() { @@ -47,7 +47,6 @@ export class DiscoverNoResults extends Component { shardFailures, timeFieldName, queryLanguage, - getDocLink, } = this.props; let shardFailuresMessage; @@ -226,7 +225,7 @@ export class DiscoverNoResults extends Component { queryStringSyntaxLink: ( { + return { + getServices: () => ({ + docLinks: { + links: { + query: { + luceneQuerySyntax: 'documentation-link', + }, + }, + }, + }), + }; +}); + +beforeEach(() => { + jest.clearAllMocks(); +}); describe('DiscoverNoResults', () => { describe('props', () => { describe('shardFailures', () => { test('renders failures list when there are failures', () => { - const shardFailures = [{ - index: 'A', - shard: '1', - reason: { reason: 'Awful error' }, - }, { - index: 'B', - shard: '2', - reason: { reason: 'Bad error' }, - }]; + const shardFailures = [ + { + index: 'A', + shard: '1', + reason: { reason: 'Awful error' }, + }, + { + index: 'B', + shard: '2', + reason: { reason: 'Bad error' }, + }, + ]; - const component = renderWithIntl( - ''} - /> - ); + const component = renderWithIntl(); expect(component).toMatchSnapshot(); }); @@ -51,12 +65,7 @@ describe('DiscoverNoResults', () => { test(`doesn't render failures list when there are no failures`, () => { const shardFailures = []; - const component = renderWithIntl( - ''} - /> - ); + const component = renderWithIntl(); expect(component).toMatchSnapshot(); }); @@ -64,12 +73,7 @@ describe('DiscoverNoResults', () => { describe('timeFieldName', () => { test('renders time range feedback', () => { - const component = renderWithIntl( - ''} - /> - ); + const component = renderWithIntl(); expect(component).toMatchSnapshot(); }); @@ -78,10 +82,7 @@ describe('DiscoverNoResults', () => { describe('queryLanguage', () => { test('supports lucene and renders doc link', () => { const component = renderWithIntl( - 'documentation-link'} - /> + 'documentation-link'} /> ); expect(component).toMatchSnapshot(); diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/directives/unsupported_index_pattern.js b/src/legacy/core_plugins/kibana/public/discover/angular/directives/unsupported_index_pattern.js index ac26203dafc4a..b1c1c47e39291 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/directives/unsupported_index_pattern.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/directives/unsupported_index_pattern.js @@ -18,7 +18,6 @@ */ import React, { Fragment } from 'react'; - import { EuiCallOut, EuiFlexGroup, diff --git a/src/legacy/core_plugins/kibana/public/discover/index.html b/src/legacy/core_plugins/kibana/public/discover/angular/discover.html similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/index.html rename to src/legacy/core_plugins/kibana/public/discover/angular/discover.html diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/discover.js b/src/legacy/core_plugins/kibana/public/discover/angular/discover.js index 840152fc40ced..ed5049aa912e0 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/discover.js @@ -18,63 +18,65 @@ */ import _ from 'lodash'; -import { i18n } from '@kbn/i18n'; import React from 'react'; -import angular from 'angular'; import { Subscription } from 'rxjs'; import moment from 'moment'; -import chrome from 'ui/chrome'; import dateMath from '@elastic/datemath'; +import { i18n } from '@kbn/i18n'; +import '../saved_searches/saved_searches'; +import '../components/field_chooser/field_chooser'; // doc table -import '../doc_table'; -import { getSort } from '../doc_table/lib/get_sort'; -import { getSortForSearchSource } from '../doc_table/lib/get_sort_for_search_source'; -import * as columnActions from '../doc_table/actions/columns'; -import * as filterActions from '../doc_table/actions/filter'; - -import 'ui/directives/listen'; -import 'ui/visualize'; -import 'ui/fixed_scroll'; -import 'ui/index_patterns'; -import 'ui/state_management/app_state'; -import { timefilter } from 'ui/timefilter'; -import { hasSearchStategyForIndexPattern, isDefaultTypeIndexPattern } from 'ui/courier'; -import { toastNotifications } from 'ui/notify'; -import { VisProvider } from 'ui/vis'; -import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter'; -import { vislibSeriesResponseHandlerProvider } from 'ui/vis/response_handlers/vislib'; -import { docTitle } from 'ui/doc_title'; -import { intervalOptions } from 'ui/agg_types/buckets/_interval_options'; -import { stateMonitorFactory } from 'ui/state_management/state_monitor_factory'; -import uiRoutes from 'ui/routes'; -import { uiModules } from 'ui/modules'; -import indexTemplate from '../index.html'; -import { StateProvider } from 'ui/state_management/state'; -import { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; -import { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; -import { getFilterGenerator } from 'ui/filter_manager'; - -import { getDocLink } from 'ui/documentation_links'; -import '../components/fetch_error'; -import { getPainlessError } from './get_painless_error'; -import { showShareContextMenu, ShareContextMenuExtensionsRegistryProvider } from 'ui/share'; -import { getUnhashableStatesProvider } from 'ui/state_management/state_hashing'; -import { Inspector } from 'ui/inspector'; -import { RequestAdapter } from 'ui/inspector/adapters'; -import { getRequestInspectorStats, getResponseInspectorStats } from 'ui/courier/utils/courier_inspector_utils'; +import './doc_table'; +import { getSort } from './doc_table/lib/get_sort'; +import { getSortForSearchSource } from './doc_table/lib/get_sort_for_search_source'; +import * as columnActions from './doc_table/actions/columns'; +import * as filterActions from './doc_table/actions/filter'; + +import indexTemplate from './discover.html'; import { showOpenSearchPanel } from '../top_nav/show_open_search_panel'; -import { tabifyAggResponse } from 'ui/agg_response/tabify'; -import { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal'; -import { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal'; -import { getRootBreadcrumbs, getSavedSearchBreadcrumbs } from '../breadcrumbs'; -import { buildVislibDimensions } from 'ui/visualize/loader/pipeline_helpers/build_pipeline'; -import 'ui/capabilities/route_setup'; import { addHelpMenuToAppChrome } from '../components/help_menu/help_menu_util'; +import '../components/fetch_error'; +import { getPainlessError } from './get_painless_error'; +import { + angular, + buildVislibDimensions, + getFilterGenerator, + getRequestInspectorStats, + getResponseInspectorStats, + getServices, + getUnhashableStatesProvider, + hasSearchStategyForIndexPattern, + intervalOptions, + isDefaultTypeIndexPattern, + migrateLegacyQuery, + RequestAdapter, + showSaveModal, + showShareContextMenu, + stateMonitorFactory, + subscribeWithScope, + tabifyAggResponse, + vislibSeriesResponseHandlerProvider, + VisProvider, + SavedObjectSaveModal, +} from '../kibana_services'; + +const { + chrome, + docTitle, + FilterBarQueryFilterProvider, + ShareContextMenuExtensionsRegistryProvider, + StateProvider, + timefilter, + toastNotifications, + uiModules, + uiRoutes, +} = getServices(); +import { getRootBreadcrumbs, getSavedSearchBreadcrumbs } from '../breadcrumbs'; import { extractTimeFilter, changeTimeFilter } from '../../../../data/public'; import { start as data } from '../../../../data/public/legacy'; -import { npStart } from 'ui/new_platform'; + const { savedQueryService } = data.search.services; @@ -151,7 +153,7 @@ uiRoutes return savedSearches.get(savedSearchId) .then((savedSearch) => { if (savedSearchId) { - npStart.core.chrome.recentlyAccessed.add( + chrome.recentlyAccessed.add( savedSearch.getFullPath(), savedSearch.title, savedSearchId); @@ -211,8 +213,6 @@ function discoverController( mode: 'absolute', }); }; - - $scope.getDocLink = getDocLink; $scope.intervalOptions = intervalOptions; $scope.showInterval = false; $scope.minimumVisibleRows = 50; @@ -354,7 +354,7 @@ function discoverController( }), testId: 'openInspectorButton', run() { - Inspector.open(inspectorAdapters, { + getServices().inspector.open(inspectorAdapters, { title: savedSearch.title }); } @@ -401,12 +401,12 @@ function discoverController( }); if (savedSearch.id && savedSearch.title) { - chrome.breadcrumbs.set([{ + chrome.setBreadcrumbs([{ text: discoverBreadcrumbsTitle, href: '#/discover', }, { text: savedSearch.title }]); } else { - chrome.breadcrumbs.set([{ + chrome.setBreadcrumbs([{ text: discoverBreadcrumbsTitle, }]); } diff --git a/src/legacy/core_plugins/kibana/public/discover/doc/index.html b/src/legacy/core_plugins/kibana/public/discover/angular/doc.html similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/doc/index.html rename to src/legacy/core_plugins/kibana/public/discover/angular/doc.html diff --git a/src/legacy/core_plugins/kibana/public/discover/doc/index.ts b/src/legacy/core_plugins/kibana/public/discover/angular/doc.ts similarity index 71% rename from src/legacy/core_plugins/kibana/public/discover/doc/index.ts rename to src/legacy/core_plugins/kibana/public/discover/angular/doc.ts index b969bd6a48126..e6c890c9a66a2 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc/index.ts +++ b/src/legacy/core_plugins/kibana/public/discover/angular/doc.ts @@ -16,14 +16,25 @@ * specific language governing permissions and limitations * under the License. */ -import uiRoutes from 'ui/routes'; -import { IndexPatterns } from 'ui/index_patterns'; -import { timefilter } from 'ui/timefilter'; +import { getServices, IndexPatterns } from '../kibana_services'; // @ts-ignore -import { getRootBreadcrumbs } from 'plugins/kibana/discover/breadcrumbs'; -// @ts-ignore -import html from './index.html'; -import './doc_directive'; +import { getRootBreadcrumbs } from '../breadcrumbs'; +import html from './doc.html'; +import { Doc } from '../doc/doc'; +const { uiRoutes, uiModules, wrapInI18nContext, timefilter } = getServices(); +uiModules.get('apps/discover').directive('discoverDoc', function(reactDirective: any) { + return reactDirective( + wrapInI18nContext(Doc), + [ + ['id', { watchDepth: 'value' }], + ['index', { watchDepth: 'value' }], + ['indexPatternId', { watchDepth: 'reference' }], + ['indexPatternService', { watchDepth: 'reference' }], + ['esClient', { watchDepth: 'reference' }], + ], + { restrict: 'E' } + ); +}); uiRoutes // the old, pre 8.0 route, no longer used, keep it to stay compatible diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/__tests__/actions/filter.js b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/__tests__/actions/filter.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/doc_table/__tests__/actions/filter.js rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/__tests__/actions/filter.js diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/__tests__/doc_table.js b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/__tests__/doc_table.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/doc_table/__tests__/doc_table.js rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/__tests__/doc_table.js diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/__tests__/lib/get_sort.js b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/__tests__/lib/get_sort.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/doc_table/__tests__/lib/get_sort.js rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/__tests__/lib/get_sort.js diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/__tests__/lib/rows_headers.js b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/__tests__/lib/rows_headers.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/doc_table/__tests__/lib/rows_headers.js rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/__tests__/lib/rows_headers.js diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/_doc_table.scss b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/_doc_table.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/doc_table/_doc_table.scss rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/_doc_table.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/_index.scss b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/_index.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/doc_table/_index.scss rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/_index.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/actions/columns.ts b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/actions/columns.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/doc_table/actions/columns.ts rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/actions/columns.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/actions/filter.js b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/actions/filter.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/doc_table/actions/filter.js rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/actions/filter.js diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/components/_index.scss b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/_index.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/doc_table/components/_index.scss rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/_index.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/components/_table_header.scss b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/_table_header.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/doc_table/components/_table_header.scss rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/_table_header.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/components/pager/__snapshots__/tool_bar_pager_buttons.test.tsx.snap b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/__snapshots__/tool_bar_pager_buttons.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/doc_table/components/pager/__snapshots__/tool_bar_pager_buttons.test.tsx.snap rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/__snapshots__/tool_bar_pager_buttons.test.tsx.snap diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/components/pager/__snapshots__/tool_bar_pager_text.test.tsx.snap b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/__snapshots__/tool_bar_pager_text.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/doc_table/components/pager/__snapshots__/tool_bar_pager_text.test.tsx.snap rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/__snapshots__/tool_bar_pager_text.test.tsx.snap diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/components/pager/index.js b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/index.js similarity index 91% rename from src/legacy/core_plugins/kibana/public/discover/doc_table/components/pager/index.js rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/index.js index e6d638d88bc27..7462de544dbce 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc_table/components/pager/index.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/index.js @@ -16,11 +16,11 @@ * specific language governing permissions and limitations * under the License. */ - -import { uiModules } from 'ui/modules'; +import { getServices } from '../../../../kibana_services'; import { ToolBarPagerText } from './tool_bar_pager_text'; import { ToolBarPagerButtons } from './tool_bar_pager_buttons'; -import { wrapInI18nContext } from 'ui/i18n'; + +const { wrapInI18nContext, uiModules } = getServices(); const app = uiModules.get('kibana'); diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/components/pager/tool_bar_pager_buttons.test.tsx b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/tool_bar_pager_buttons.test.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/doc_table/components/pager/tool_bar_pager_buttons.test.tsx rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/tool_bar_pager_buttons.test.tsx diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/components/pager/tool_bar_pager_buttons.tsx b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/tool_bar_pager_buttons.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/doc_table/components/pager/tool_bar_pager_buttons.tsx rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/tool_bar_pager_buttons.tsx diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/components/pager/tool_bar_pager_text.test.tsx b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/tool_bar_pager_text.test.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/doc_table/components/pager/tool_bar_pager_text.test.tsx rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/tool_bar_pager_text.test.tsx diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/components/pager/tool_bar_pager_text.tsx b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/tool_bar_pager_text.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/doc_table/components/pager/tool_bar_pager_text.tsx rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/pager/tool_bar_pager_text.tsx diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/components/table_header.ts b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header.ts similarity index 93% rename from src/legacy/core_plugins/kibana/public/discover/doc_table/components/table_header.ts rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header.ts index e054120c08474..f447c54507729 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc_table/components/table_header.ts +++ b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header.ts @@ -17,10 +17,9 @@ * under the License. */ import { wrapInI18nContext } from 'ui/i18n'; -// @ts-ignore -import { uiModules } from 'ui/modules'; +import { getServices } from '../../../kibana_services'; import { TableHeader } from './table_header/table_header'; -const module = uiModules.get('app/discover'); +const module = getServices().uiModules.get('app/discover'); module.directive('kbnTableHeader', function(reactDirective: any, config: any) { return reactDirective( diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/components/table_header/__snapshots__/table_header.test.tsx.snap b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header/__snapshots__/table_header.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/doc_table/components/table_header/__snapshots__/table_header.test.tsx.snap rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header/__snapshots__/table_header.test.tsx.snap diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/components/table_header/helpers.tsx b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header/helpers.tsx similarity index 94% rename from src/legacy/core_plugins/kibana/public/discover/doc_table/components/table_header/helpers.tsx rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header/helpers.tsx index ddf960091d476..80f963c8ccb3e 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc_table/components/table_header/helpers.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header/helpers.tsx @@ -16,10 +16,9 @@ * specific language governing permissions and limitations * under the License. */ - -import { IndexPattern } from 'ui/index_patterns'; +import { IndexPattern } from '../../../../kibana_services'; // @ts-ignore -import { shortenDottedString } from '../../../../../common/utils/shorten_dotted_string'; +import { shortenDottedString } from '../../../../../../common/utils/shorten_dotted_string'; export type SortOrder = [string, 'asc' | 'desc']; export interface ColumnProps { diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/components/table_header/table_header.test.tsx b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header/table_header.test.tsx similarity index 98% rename from src/legacy/core_plugins/kibana/public/discover/doc_table/components/table_header/table_header.test.tsx rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header/table_header.test.tsx index ea2c65b1b8487..09ba77c7c4999 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc_table/components/table_header/table_header.test.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header/table_header.test.tsx @@ -23,7 +23,7 @@ import { TableHeader } from './table_header'; // @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; import { SortOrder } from './helpers'; -import { IndexPattern, FieldType } from 'ui/index_patterns'; +import { IndexPattern, FieldType } from '../../../../kibana_services'; function getMockIndexPattern() { return ({ diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/components/table_header/table_header.tsx b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header/table_header.tsx similarity index 91% rename from src/legacy/core_plugins/kibana/public/discover/doc_table/components/table_header/table_header.tsx rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header/table_header.tsx index abc8077afb669..71674710ac855 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc_table/components/table_header/table_header.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/angular/doc_table/components/table_header/table_header.tsx @@ -17,9 +17,8 @@ * under the License. */ import React from 'react'; -import { IndexPattern } from 'ui/index_patterns'; +import { IndexPattern } from '../../../../kibana_services'; // @ts-ignore -import { shortenDottedString } from '../../../../../common/utils/shorten_dotted_string'; import { TableHeaderColumn } from './table_header_column'; import { SortOrder, getDisplayedColumns } from './helpers'; @@ -48,7 +47,7 @@ export function TableHeader({ return ( - + {displayedColumns.map(col => { return ( { return { diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_directive.ts b/src/legacy/core_plugins/kibana/public/discover/angular/doc_viewer.ts similarity index 90% rename from src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_directive.ts rename to src/legacy/core_plugins/kibana/public/discover/angular/doc_viewer.ts index fa6145c45f55f..c13c354528413 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_directive.ts +++ b/src/legacy/core_plugins/kibana/public/discover/angular/doc_viewer.ts @@ -18,8 +18,10 @@ */ // @ts-ignore -import { uiModules } from 'ui/modules'; -import { DocViewer } from './doc_viewer'; +import { getServices } from '../kibana_services'; +import { DocViewer } from '../doc_viewer/doc_viewer'; + +const { uiModules } = getServices(); uiModules.get('apps/discover').directive('docViewer', (reactDirective: any) => { return reactDirective( diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/index.ts b/src/legacy/core_plugins/kibana/public/discover/angular/index.ts new file mode 100644 index 0000000000000..5bae0d9d551e5 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/angular/index.ts @@ -0,0 +1,23 @@ +/* + * 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 './discover'; +import './doc'; +import './context'; +import './doc_viewer'; +import './directives'; diff --git a/src/legacy/core_plugins/kibana/public/discover/breadcrumbs.js b/src/legacy/core_plugins/kibana/public/discover/breadcrumbs.ts similarity index 88% rename from src/legacy/core_plugins/kibana/public/discover/breadcrumbs.js rename to src/legacy/core_plugins/kibana/public/discover/breadcrumbs.ts index 1220c99b5ee56..51e0dcba1cad0 100644 --- a/src/legacy/core_plugins/kibana/public/discover/breadcrumbs.js +++ b/src/legacy/core_plugins/kibana/public/discover/breadcrumbs.ts @@ -23,18 +23,18 @@ export function getRootBreadcrumbs() { return [ { text: i18n.translate('kbn.discover.rootBreadcrumb', { - defaultMessage: 'Discover' + defaultMessage: 'Discover', }), - href: '#/discover' - } + href: '#/discover', + }, ]; } -export function getSavedSearchBreadcrumbs($route) { +export function getSavedSearchBreadcrumbs($route: any) { return [ ...getRootBreadcrumbs(), { text: $route.current.locals.savedSearch.id, - } + }, ]; } diff --git a/src/legacy/core_plugins/kibana/public/discover/components/fetch_error/fetch_error.js b/src/legacy/core_plugins/kibana/public/discover/components/fetch_error/fetch_error.js index 670e9446c6e4f..612ca860f8031 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/fetch_error/fetch_error.js +++ b/src/legacy/core_plugins/kibana/public/discover/components/fetch_error/fetch_error.js @@ -16,21 +16,11 @@ * specific language governing permissions and limitations * under the License. */ - -import 'ngreact'; import React, { Fragment } from 'react'; -import { uiModules } from 'ui/modules'; -import { wrapInI18nContext } from 'ui/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { npStart } from 'ui/new_platform'; - -import { - EuiFlexGroup, - EuiFlexItem, - EuiCallOut, - EuiCodeBlock, - EuiSpacer, -} from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiCallOut, EuiCodeBlock, EuiSpacer } from '@elastic/eui'; +import { getServices } from '../../kibana_services'; +const { uiModules, wrapInI18nContext, chrome } = getServices(); const DiscoverFetchError = ({ fetchError }) => { if (!fetchError) { @@ -40,7 +30,7 @@ const DiscoverFetchError = ({ fetchError }) => { let body; if (fetchError.lang === 'painless') { - const managementUrl = npStart.core.chrome.navLinks.get('kibana:management').url; + const managementUrl = chrome.navLinks.get('kibana:management').url; const url = `${managementUrl}/kibana/index_patterns`; body = ( @@ -51,10 +41,12 @@ const DiscoverFetchError = ({ fetchError }) => { in {managementLink}, under the {scriptedFields} tab." values={{ fetchErrorScript: `'${fetchError.script}'`, - scriptedFields: , + scriptedFields: ( + + ), managementLink: ( { defaultMessage="Management > Index Patterns" /> - ) + ), }} />

@@ -75,16 +67,10 @@ const DiscoverFetchError = ({ fetchError }) => { - + {body} - - {fetchError.error} - + {fetchError.error} @@ -96,4 +82,6 @@ const DiscoverFetchError = ({ fetchError }) => { const app = uiModules.get('apps/discover', ['react']); -app.directive('discoverFetchError', reactDirective => reactDirective(wrapInI18nContext(DiscoverFetchError))); +app.directive('discoverFetchError', reactDirective => + reactDirective(wrapInI18nContext(DiscoverFetchError)) +); diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field.js b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field.js index f7469d0142d57..cfcb654077152 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field.js +++ b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field.js @@ -18,15 +18,15 @@ */ import $ from 'jquery'; +import _ from 'lodash'; import { i18n } from '@kbn/i18n'; +import { getServices } from '../../kibana_services'; import html from './discover_field.html'; -import _ from 'lodash'; import 'ui/directives/css_truncate'; import 'ui/directives/field_name'; import './string_progress_bar'; import detailsHtml from './lib/detail_views/string.html'; -import { capabilities } from 'ui/capabilities'; -import { uiModules } from 'ui/modules'; +const { uiModules, capabilities } = getServices(); const app = uiModules.get('apps/discover'); app.directive('discoverField', function ($compile) { @@ -78,7 +78,7 @@ app.directive('discoverField', function ($compile) { }; - $scope.canVisualize = capabilities.get().visualize.show; + $scope.canVisualize = capabilities.visualize.show; $scope.toggleDisplay = function (field) { if (field.display) { diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search_directive.ts b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search_directive.ts index 8af23caedd78a..2e7dd3e210ef8 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search_directive.ts +++ b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_field_search_directive.ts @@ -17,10 +17,11 @@ * under the License. */ // @ts-ignore -import { uiModules } from 'ui/modules'; -import { wrapInI18nContext } from 'ui/i18n'; +import { getServices } from '../../kibana_services'; import { DiscoverFieldSearch } from './discover_field_search'; +const { wrapInI18nContext, uiModules } = getServices(); + const app = uiModules.get('apps/discover'); app.directive('discoverFieldSearch', function(reactDirective: any) { diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_index_pattern_directive.ts b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_index_pattern_directive.ts index 938d6cc226f2f..5e3f678e388ad 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_index_pattern_directive.ts +++ b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/discover_index_pattern_directive.ts @@ -17,10 +17,11 @@ * under the License. */ // @ts-ignore -import { uiModules } from 'ui/modules'; -import { wrapInI18nContext } from 'ui/i18n'; +import { getServices } from '../../kibana_services'; import { DiscoverIndexPattern } from './discover_index_pattern'; +const { wrapInI18nContext, uiModules } = getServices(); + const app = uiModules.get('apps/discover'); app.directive('discoverIndexPatternSelect', function(reactDirective: any) { diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/field_chooser.js b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/field_chooser.js index 3e0172dec1ff4..99a63efc0e0fc 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/field_chooser.js +++ b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/field_chooser.js @@ -16,20 +16,22 @@ * specific language governing permissions and limitations * under the License. */ - -import 'ui/directives/css_truncate'; +//field_name directive will be replaced very soon import 'ui/directives/field_name'; import './discover_field'; -import 'ui/angular_ui_select'; import './discover_field_search_directive'; import './discover_index_pattern_directive'; import _ from 'lodash'; import $ from 'jquery'; import rison from 'rison-node'; import { fieldCalculator } from './lib/field_calculator'; -import { FieldList } from 'ui/index_patterns'; -import { uiModules } from 'ui/modules'; +import { + getServices, + FieldList +} from '../../kibana_services'; import fieldChooserTemplate from './field_chooser.html'; + +const { uiModules } = getServices(); const app = uiModules.get('apps/discover'); app.directive('discFieldChooser', function ($location, config, $route) { diff --git a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/string_progress_bar.js b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/string_progress_bar.js index ae00df6dfbbf8..ca3a47cad5075 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/string_progress_bar.js +++ b/src/legacy/core_plugins/kibana/public/discover/components/field_chooser/string_progress_bar.js @@ -16,13 +16,8 @@ * specific language governing permissions and limitations * under the License. */ - - -import { wrapInI18nContext } from 'ui/i18n'; -import { uiModules } from 'ui/modules'; - import React from 'react'; - +import { getServices } from '../../kibana_services'; import { EuiFlexGroup, EuiFlexItem, @@ -31,6 +26,7 @@ import { EuiToolTip, } from '@elastic/eui'; +const { wrapInI18nContext, uiModules } = getServices(); const module = uiModules.get('discover/field_chooser'); function StringFieldProgressBar(props) { diff --git a/src/legacy/core_plugins/kibana/public/discover/components/help_menu/help_menu.js b/src/legacy/core_plugins/kibana/public/discover/components/help_menu/help_menu.js index 95db1b686f7aa..ad68e55e71622 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/help_menu/help_menu.js +++ b/src/legacy/core_plugins/kibana/public/discover/components/help_menu/help_menu.js @@ -20,7 +20,8 @@ import React, { Fragment, PureComponent } from 'react'; import { EuiButton, EuiHorizontalRule, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links'; +import { getServices } from '../../kibana_services'; +const { docLinks } = getServices(); export class HelpMenu extends PureComponent { render() { @@ -31,7 +32,7 @@ export class HelpMenu extends PureComponent { diff --git a/src/legacy/core_plugins/kibana/public/discover/components/help_menu/help_menu_util.js b/src/legacy/core_plugins/kibana/public/discover/components/help_menu/help_menu_util.js index aeabff2d97007..58a92193de63e 100644 --- a/src/legacy/core_plugins/kibana/public/discover/components/help_menu/help_menu_util.js +++ b/src/legacy/core_plugins/kibana/public/discover/components/help_menu/help_menu_util.js @@ -22,7 +22,7 @@ import { render, unmountComponentAtNode } from 'react-dom'; import { HelpMenu } from './help_menu'; export function addHelpMenuToAppChrome(chrome) { - chrome.helpExtension.set(domElement => { + chrome.setHelpExtension(domElement => { render(, domElement); return () => { unmountComponentAtNode(domElement); diff --git a/src/legacy/core_plugins/kibana/public/discover/context/README.md b/src/legacy/core_plugins/kibana/public/discover/context/README.md new file mode 100644 index 0000000000000..18ba118b4da79 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/context/README.md @@ -0,0 +1,4 @@ +# DISCOVER CONTEXT + +Placeholder for Discover's context functionality, that's currently in [../angular/context](../angular/context). +Once fully de-angularized it should be moved to this location \ No newline at end of file diff --git a/src/legacy/core_plugins/kibana/public/discover/doc/doc.test.tsx b/src/legacy/core_plugins/kibana/public/discover/doc/doc.test.tsx index 6612097620b44..b3efd23ea48d0 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc/doc.test.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/doc/doc.test.tsx @@ -24,6 +24,27 @@ import { ReactWrapper } from 'enzyme'; import { findTestSubject } from '@elastic/eui/lib/test'; import { Doc, DocProps } from './doc'; +jest.mock('../doc_viewer/doc_viewer', () => ({ + DocViewer: 'test', +})); + +jest.mock('../kibana_services', () => { + return { + getServices: () => ({ + metadata: { + branch: 'test', + }, + getDocViewsSorted: () => { + return []; + }, + }), + }; +}); + +beforeEach(() => { + jest.clearAllMocks(); +}); + // Suppress warnings about "act" until we use React 16.9 /* eslint-disable no-console */ const originalError = console.error; @@ -68,30 +89,30 @@ async function mountDoc(search: () => void, update = false, indexPatternGetter: } describe('Test of of Discover', () => { - it('renders loading msg', async () => { + test('renders loading msg', async () => { const comp = await mountDoc(jest.fn()); expect(findTestSubject(comp, 'doc-msg-loading').length).toBe(1); }); - it('renders IndexPattern notFound msg', async () => { + test('renders IndexPattern notFound msg', async () => { const indexPatternGetter = jest.fn(() => Promise.reject({ savedObjectId: '007' })); const comp = await mountDoc(jest.fn(), true, indexPatternGetter); expect(findTestSubject(comp, 'doc-msg-notFoundIndexPattern').length).toBe(1); }); - it('renders notFound msg', async () => { + test('renders notFound msg', async () => { const search = jest.fn(() => Promise.reject({ status: 404 })); const comp = await mountDoc(search, true); expect(findTestSubject(comp, 'doc-msg-notFound').length).toBe(1); }); - it('renders error msg', async () => { + test('renders error msg', async () => { const search = jest.fn(() => Promise.reject('whatever')); const comp = await mountDoc(search, true); expect(findTestSubject(comp, 'doc-msg-error').length).toBe(1); }); - it('renders elasticsearch hit ', async () => { + test('renders elasticsearch hit ', async () => { const hit = { hits: { total: 1, hits: [{ _id: 1, _source: { test: 1 } }] } }; const search = jest.fn(() => Promise.resolve(hit)); const comp = await mountDoc(search, true); diff --git a/src/legacy/core_plugins/kibana/public/discover/doc/doc.tsx b/src/legacy/core_plugins/kibana/public/discover/doc/doc.tsx index 1f2d2fc532b57..0e0e6ed110ca6 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc/doc.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/doc/doc.tsx @@ -19,11 +19,9 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiCallOut, EuiLink, EuiLoadingSpinner, EuiPageContent } from '@elastic/eui'; -import { IndexPatterns } from 'ui/index_patterns'; -import { metadata } from 'ui/metadata'; -import { ElasticSearchHit } from 'ui/registry/doc_views_types'; -import { DocViewer } from '../doc_viewer'; +import { DocViewer } from '../doc_viewer/doc_viewer'; import { ElasticRequestState, useEsDocSearch } from './use_es_doc_search'; +import { IndexPatterns, ElasticSearchHit, getServices } from '../kibana_services'; export interface ElasticSearchResult { hits: { @@ -117,7 +115,9 @@ export function Doc(props: DocProps) { values={{ indexName: props.index }} />{' '} { + return { + getServices: () => ({ + docViewsRegistry: { + getDocViewsSorted: (hit: any) => { + return mockGetDocViewsSorted(hit); + }, + }, + }), + }; +}); beforeEach(() => { emptyDocViews(); + jest.clearAllMocks(); }); test('Render with 3 different tabs', () => { diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer.tsx b/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer.tsx index 1c7aba9bcd7b2..aa737ebd8dcf1 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer.tsx @@ -18,7 +18,7 @@ */ import React from 'react'; import { EuiTabbedContent } from '@elastic/eui'; -import { getDocViewsSorted, DocViewRenderProps } from 'ui/registry/doc_views'; +import { getServices, DocViewRenderProps } from '../kibana_services'; import { DocViewerTab } from './doc_viewer_tab'; /** @@ -28,21 +28,24 @@ import { DocViewerTab } from './doc_viewer_tab'; * a `render` function. */ export function DocViewer(renderProps: DocViewRenderProps) { - const tabs = getDocViewsSorted(renderProps.hit).map(({ title, render, component }, idx) => { - return { - id: title, - name: title, - content: ( - - ), - }; - }); + const { docViewsRegistry } = getServices(); + const tabs = docViewsRegistry + .getDocViewsSorted(renderProps.hit) + .map(({ title, render, component }, idx) => { + return { + id: title, + name: title, + content: ( + + ), + }; + }); if (!tabs.length) { // There there's a minimum of 2 tabs active in Discover. diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_render_tab.test.tsx b/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_render_tab.test.tsx index 3bb59a8dc958c..5fa2d24dfa04c 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_render_tab.test.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_render_tab.test.tsx @@ -19,7 +19,7 @@ import React from 'react'; import { mount } from 'enzyme'; import { DocViewRenderTab } from './doc_viewer_render_tab'; -import { DocViewRenderProps } from 'ui/registry/doc_views'; +import { DocViewRenderProps } from '../kibana_services'; test('Mounting and unmounting DocViewerRenderTab', () => { const unmountFn = jest.fn(); diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_render_tab.tsx b/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_render_tab.tsx index 185ff163dad2a..750ef6b6061e1 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_render_tab.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_render_tab.tsx @@ -17,7 +17,7 @@ * under the License. */ import React, { useRef, useEffect } from 'react'; -import { DocViewRenderFn, DocViewRenderProps } from 'ui/registry/doc_views'; +import { DocViewRenderFn, DocViewRenderProps } from '../kibana_services'; interface Props { render: DocViewRenderFn; diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_tab.tsx b/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_tab.tsx index 0b25421d8aff3..3721ba5818d41 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_tab.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_tab.tsx @@ -18,7 +18,7 @@ */ import React from 'react'; import { I18nProvider } from '@kbn/i18n/react'; -import { DocViewRenderProps, DocViewRenderFn } from 'ui/registry/doc_views'; +import { DocViewRenderProps, DocViewRenderFn } from '../kibana_services'; import { DocViewRenderTab } from './doc_viewer_render_tab'; import { DocViewerError } from './doc_viewer_render_error'; diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/constants.ts b/src/legacy/core_plugins/kibana/public/discover/embeddable/constants.ts new file mode 100644 index 0000000000000..cdb8a6ad3ad46 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/embeddable/constants.ts @@ -0,0 +1,19 @@ +/* + * 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 SEARCH_EMBEDDABLE_TYPE = 'search'; diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/index.ts b/src/legacy/core_plugins/kibana/public/discover/embeddable/index.ts index 3138008f3e3a0..beeb6a7338f9d 100644 --- a/src/legacy/core_plugins/kibana/public/discover/embeddable/index.ts +++ b/src/legacy/core_plugins/kibana/public/discover/embeddable/index.ts @@ -20,3 +20,4 @@ export * from './types'; export * from './search_embeddable_factory'; export * from './search_embeddable'; +export { SEARCH_EMBEDDABLE_TYPE } from './constants'; diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts index eaec11ff893ed..5f3ebd6d22e24 100644 --- a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts +++ b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable.ts @@ -16,42 +16,38 @@ * specific language governing permissions and limitations * under the License. */ - -// @ts-ignore -import { getFilterGenerator } from 'ui/filter_manager'; -import angular from 'angular'; import _ from 'lodash'; -import { SearchSource } from 'ui/courier'; -import { - getRequestInspectorStats, - getResponseInspectorStats, -} from 'ui/courier/utils/courier_inspector_utils'; -import { IndexPattern } from 'ui/index_patterns'; -import { RequestAdapter } from 'ui/inspector/adapters'; -import { Adapters } from 'ui/inspector/types'; -import { Subscription } from 'rxjs'; import * as Rx from 'rxjs'; +import { Subscription } from 'rxjs'; import { Filter, FilterStateStore } from '@kbn/es-query'; -import chrome from 'ui/chrome'; import { i18n } from '@kbn/i18n'; -import { toastNotifications } from 'ui/notify'; -import { TimeRange } from 'src/plugins/data/public'; import { TExecuteTriggerActions } from 'src/plugins/ui_actions/public'; import { setup as data } from '../../../../data/public/legacy'; -import { Query, onlyDisabledFiltersChanged, getTime } from '../../../../data/public'; +import { getTime, onlyDisabledFiltersChanged, Query } from '../../../../data/public'; import { APPLY_FILTER_TRIGGER, - Embeddable, Container, + Embeddable, } from '../../../../embeddable_api/public/np_ready/public'; -import * as columnActions from '../doc_table/actions/columns'; +import * as columnActions from '../angular/doc_table/actions/columns'; import { SavedSearch } from '../types'; import searchTemplate from './search_template.html'; import { ISearchEmbeddable, SearchInput, SearchOutput } from './types'; -import { SortOrder } from '../doc_table/components/table_header/helpers'; -import { getSortForSearchSource } from '../doc_table/lib/get_sort_for_search_source'; - -const config = chrome.getUiSettingsClient(); +import { SortOrder } from '../angular/doc_table/components/table_header/helpers'; +import { getSortForSearchSource } from '../angular/doc_table/lib/get_sort_for_search_source'; +import { + Adapters, + angular, + getFilterGenerator, + getRequestInspectorStats, + getResponseInspectorStats, + getServices, + IndexPattern, + RequestAdapter, + SearchSource, +} from '../kibana_services'; +import { TimeRange } from '../../../../../../plugins/data/public'; +import { SEARCH_EMBEDDABLE_TYPE } from './constants'; interface SearchScope extends ng.IScope { columns?: string[]; @@ -92,8 +88,6 @@ interface SearchEmbeddableConfig { queryFilter: unknown; } -export const SEARCH_EMBEDDABLE_TYPE = 'search'; - export class SearchEmbeddable extends Embeddable implements ISearchEmbeddable { private readonly savedSearch: SavedSearch; @@ -277,7 +271,7 @@ export class SearchEmbeddable extends Embeddable if (this.abortController) this.abortController.abort(); this.abortController = new AbortController(); - searchSource.setField('size', config.get('discover:sampleSize')); + searchSource.setField('size', getServices().uiSettings.get('discover:sampleSize')); searchSource.setField( 'sort', getSortForSearchSource(this.searchScope.sort, this.searchScope.indexPattern) @@ -319,7 +313,7 @@ export class SearchEmbeddable extends Embeddable // If the fetch was aborted, no need to surface this in the UI if (error.name === 'AbortError') return; - toastNotifications.addError(error, { + getServices().toastNotifications.addError(error, { title: i18n.translate('kbn.embeddable.errorTitle', { defaultMessage: 'Error fetching data', }), diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable_factory.ts b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable_factory.ts index d7b51b39e2a16..1939cc7060621 100644 --- a/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable_factory.ts +++ b/src/legacy/core_plugins/kibana/public/discover/embeddable/search_embeddable_factory.ts @@ -16,24 +16,21 @@ * specific language governing permissions and limitations * under the License. */ - -import '../doc_table'; -import { capabilities } from 'ui/capabilities'; -import { npStart, npSetup } from 'ui/new_platform'; -import { i18n } from '@kbn/i18n'; -import chrome from 'ui/chrome'; import { IPrivate } from 'ui/private'; -import { TimeRange } from 'src/plugins/data/public'; -import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter'; +import { i18n } from '@kbn/i18n'; import { TExecuteTriggerActions } from 'src/plugins/ui_actions/public'; +import '../angular/doc_table'; +import { getServices } from '../kibana_services'; import { EmbeddableFactory, ErrorEmbeddable, Container, } from '../../../../../../plugins/embeddable/public'; +import { TimeRange } from '../../../../../../plugins/data/public'; import { SavedSearchLoader } from '../types'; -import { SearchEmbeddable, SEARCH_EMBEDDABLE_TYPE } from './search_embeddable'; +import { SearchEmbeddable } from './search_embeddable'; import { SearchInput, SearchOutput } from './types'; +import { SEARCH_EMBEDDABLE_TYPE } from './constants'; export class SearchEmbeddableFactory extends EmbeddableFactory< SearchInput, @@ -55,7 +52,7 @@ export class SearchEmbeddableFactory extends EmbeddableFactory< } public isEditable() { - return capabilities.get().discover.save as boolean; + return getServices().capabilities.discover.save as boolean; } public canCreateNew() { @@ -73,16 +70,18 @@ export class SearchEmbeddableFactory extends EmbeddableFactory< input: Partial & { id: string; timeRange: TimeRange }, parent?: Container ): Promise { - const $injector = await chrome.dangerouslyGetActiveInjector(); + const $injector = await getServices().getInjector(); const $compile = $injector.get('$compile'); const $rootScope = $injector.get('$rootScope'); const searchLoader = $injector.get('savedSearches'); - const editUrl = chrome.addBasePath(`/app/kibana${searchLoader.urlFor(savedObjectId)}`); + const editUrl = await getServices().addBasePath( + `/app/kibana${searchLoader.urlFor(savedObjectId)}` + ); const Private = $injector.get('Private'); - const queryFilter = Private(FilterBarQueryFilterProvider); + const queryFilter = Private(getServices().FilterBarQueryFilterProvider); try { const savedObject = await searchLoader.get(savedObjectId); return new SearchEmbeddable( @@ -92,7 +91,7 @@ export class SearchEmbeddableFactory extends EmbeddableFactory< $compile, editUrl, queryFilter, - editable: capabilities.get().discover.save as boolean, + editable: getServices().capabilities.discover.save as boolean, indexPatterns: _.compact([savedObject.searchSource.getField('index')]), }, input, @@ -110,5 +109,5 @@ export class SearchEmbeddableFactory extends EmbeddableFactory< } } -const factory = new SearchEmbeddableFactory(npStart.plugins.uiActions.executeTriggerActions); -npSetup.plugins.embeddable.registerEmbeddableFactory(factory.type, factory); +const factory = new SearchEmbeddableFactory(getServices().uiActions.executeTriggerActions); +getServices().embeddable.registerEmbeddableFactory(factory.type, factory); diff --git a/src/legacy/core_plugins/kibana/public/discover/embeddable/types.ts b/src/legacy/core_plugins/kibana/public/discover/embeddable/types.ts index bc46cdbe82981..db8d2afc7aff3 100644 --- a/src/legacy/core_plugins/kibana/public/discover/embeddable/types.ts +++ b/src/legacy/core_plugins/kibana/public/discover/embeddable/types.ts @@ -17,13 +17,13 @@ * under the License. */ -import { StaticIndexPattern } from 'ui/index_patterns'; import { TimeRange } from 'src/plugins/data/public'; import { Query } from 'src/legacy/core_plugins/data/public'; import { Filter } from '@kbn/es-query'; import { EmbeddableInput, EmbeddableOutput, IEmbeddable } from 'src/plugins/embeddable/public'; +import { StaticIndexPattern } from '../kibana_services'; import { SavedSearch } from '../types'; -import { SortOrder } from '../doc_table/components/table_header/helpers'; +import { SortOrder } from '../angular/doc_table/components/table_header/helpers'; export interface SearchInput extends EmbeddableInput { timeRange: TimeRange; diff --git a/src/legacy/core_plugins/kibana/public/discover/helpers/register_feature.ts b/src/legacy/core_plugins/kibana/public/discover/helpers/register_feature.ts new file mode 100644 index 0000000000000..eb8c2aec91558 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/helpers/register_feature.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 { i18n } from '@kbn/i18n'; +import { + FeatureCatalogueRegistryProvider, + FeatureCatalogueCategory, +} from 'ui/registry/feature_catalogue'; + +export function registerFeature() { + FeatureCatalogueRegistryProvider.register(() => { + return { + id: 'discover', + title: i18n.translate('kbn.discover.discoverTitle', { + defaultMessage: 'Discover', + }), + description: i18n.translate('kbn.discover.discoverDescription', { + defaultMessage: 'Interactively explore your data by querying and filtering raw documents.', + }), + icon: 'discoverApp', + path: '/app/kibana#/discover', + showOnHomePage: true, + category: FeatureCatalogueCategory.DATA, + }; + }); +} diff --git a/src/legacy/core_plugins/kibana/public/discover/index.js b/src/legacy/core_plugins/kibana/public/discover/index.js deleted file mode 100644 index e509936306275..0000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/index.js +++ /dev/null @@ -1,46 +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 './saved_searches/saved_searches'; -import { i18n } from '@kbn/i18n'; - -import './angular/directives'; -import 'ui/collapsible_sidebar'; -import './components/field_chooser/field_chooser'; -import './angular/discover'; -import './doc_table/components/table_row'; -import { FeatureCatalogueRegistryProvider, FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; -import './doc'; -import './context'; - -FeatureCatalogueRegistryProvider.register(() => { - return { - id: 'discover', - title: i18n.translate('kbn.discover.discoverTitle', { - defaultMessage: 'Discover', - }), - description: i18n.translate('kbn.discover.discoverDescription', { - defaultMessage: 'Interactively explore your data by querying and filtering raw documents.', - }), - icon: 'discoverApp', - path: '/app/kibana#/discover', - showOnHomePage: true, - category: FeatureCatalogueCategory.DATA - }; -}); diff --git a/src/legacy/core_plugins/kibana/public/discover/index.ts b/src/legacy/core_plugins/kibana/public/discover/index.ts new file mode 100644 index 0000000000000..35e48598f07a8 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/index.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 { PluginInitializer, PluginInitializerContext } from 'kibana/public'; +import { npSetup, npStart } from 'ui/new_platform'; +import { DiscoverPlugin, DiscoverSetup, DiscoverStart } from './plugin'; + +// Core will be looking for this when loading our plugin in the new platform +export const plugin: PluginInitializer = ( + initializerContext: PluginInitializerContext +) => { + return new DiscoverPlugin(initializerContext); +}; + +const pluginInstance = plugin({} as PluginInitializerContext); +export const setup = pluginInstance.setup(npSetup.core, npSetup.plugins); +export const start = pluginInstance.start(npStart.core, npStart.plugins); diff --git a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts new file mode 100644 index 0000000000000..dd0674073f442 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts @@ -0,0 +1,121 @@ +/* + * 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 'ui/collapsible_sidebar'; +import 'ui/directives/listen'; +import 'ui/fixed_scroll'; +import 'ui/directives/css_truncate'; + +import { npStart } from 'ui/new_platform'; +import chromeLegacy from 'ui/chrome'; +import angular from 'angular'; // just used in embeddables and discover controller +import uiRoutes from 'ui/routes'; +// @ts-ignore +import { uiModules } from 'ui/modules'; +import { SearchSource } from 'ui/courier'; +// @ts-ignore +import { StateProvider } from 'ui/state_management/state'; +// @ts-ignore +import { SavedObjectProvider } from 'ui/saved_objects/saved_object'; +import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry'; +import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter'; +import { timefilter } from 'ui/timefilter'; +import { ShareContextMenuExtensionsRegistryProvider } from 'ui/share'; +// @ts-ignore +import { IndexPattern, IndexPatterns } from 'ui/index_patterns'; +import { wrapInI18nContext } from 'ui/i18n'; +// @ts-ignore +import { docTitle } from 'ui/doc_title'; +// @ts-ignore +import * as docViewsRegistry from 'ui/registry/doc_views'; + +const services = { + // new plattform + addBasePath: npStart.core.http.basePath.prepend, + capabilities: npStart.core.application.capabilities, + chrome: npStart.core.chrome, + docLinks: npStart.core.docLinks, + eui_utils: npStart.plugins.eui_utils, + inspector: npStart.plugins.inspector, + metadata: npStart.core.injectedMetadata.getLegacyMetadata(), + toastNotifications: npStart.core.notifications.toasts, + uiSettings: npStart.core.uiSettings, + uiActions: npStart.plugins.uiActions, + embeddable: npStart.plugins.embeddable, + // legacy + docTitle, + docViewsRegistry, + FilterBarQueryFilterProvider, + getInjector: () => { + return chromeLegacy.dangerouslyGetActiveInjector(); + }, + SavedObjectRegistryProvider, + SavedObjectProvider, + SearchSource, + ShareContextMenuExtensionsRegistryProvider, + StateProvider, + timefilter, + uiModules, + uiRoutes, + wrapInI18nContext, +}; +export function getServices() { + return services; +} + +// EXPORT legacy static dependencies +export { angular }; +export { buildVislibDimensions } from 'ui/visualize/loader/pipeline_helpers/build_pipeline'; +// @ts-ignore +export { callAfterBindingsWorkaround } from 'ui/compat'; +// @ts-ignore +export { getFilterGenerator } from 'ui/filter_manager'; +export { + getRequestInspectorStats, + getResponseInspectorStats, +} from 'ui/courier/utils/courier_inspector_utils'; +// @ts-ignore +export { hasSearchStategyForIndexPattern, isDefaultTypeIndexPattern } from 'ui/courier'; +// @ts-ignore +export { intervalOptions } from 'ui/agg_types/buckets/_interval_options'; +// @ts-ignore +export { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; +// @ts-ignore +export { RequestAdapter } from 'ui/inspector/adapters'; +export { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal'; +export { FieldList } from 'ui/index_patterns'; +export { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal'; +export { showShareContextMenu } from 'ui/share'; +export { stateMonitorFactory } from 'ui/state_management/state_monitor_factory'; +export { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; +// @ts-ignore +export { timezoneProvider } from 'ui/vis/lib/timezone'; +// @ts-ignore +export { getUnhashableStatesProvider } from 'ui/state_management/state_hashing'; +// @ts-ignore +export { tabifyAggResponse } from 'ui/agg_response/tabify'; +// @ts-ignore +export { vislibSeriesResponseHandlerProvider } from 'ui/vis/response_handlers/vislib'; + +// EXPORT types +export { VisProvider } from 'ui/vis'; +export { StaticIndexPattern, IndexPatterns, IndexPattern, FieldType } from 'ui/index_patterns'; +export { SearchSource } from 'ui/courier'; +export { ElasticSearchHit } from 'ui/registry/doc_views_types'; +export { DocViewRenderProps, DocViewRenderFn } from 'ui/registry/doc_views'; +export { Adapters } from 'ui/inspector/types'; diff --git a/src/legacy/core_plugins/kibana/public/discover/plugin.ts b/src/legacy/core_plugins/kibana/public/discover/plugin.ts new file mode 100644 index 0000000000000..873c429bf705d --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/plugin.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 { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'kibana/public'; +import { IUiActionsStart } from 'src/plugins/ui_actions/public'; +import { registerFeature } from './helpers/register_feature'; +import './kibana_services'; +import { + Start as EmbeddableStart, + Setup as EmbeddableSetup, +} from '../../../../../plugins/embeddable/public'; + +/** + * These are the interfaces with your public contracts. You should export these + * for other plugins to use in _their_ `SetupDeps`/`StartDeps` interfaces. + * @public + */ +export type DiscoverSetup = void; +export type DiscoverStart = void; +interface DiscoverSetupPlugins { + uiActions: IUiActionsStart; + embeddable: EmbeddableSetup; +} +interface DiscoverStartPlugins { + uiActions: IUiActionsStart; + embeddable: EmbeddableStart; +} + +export class DiscoverPlugin implements Plugin { + constructor(initializerContext: PluginInitializerContext) {} + setup(core: CoreSetup, plugins: DiscoverSetupPlugins): DiscoverSetup { + registerFeature(); + require('./angular'); + } + + start(core: CoreStart, plugins: DiscoverStartPlugins): DiscoverStart { + // TODO enable this when possible, seems it broke a functional test: + // dashboard mode Dashboard View Mode Dashboard viewer can paginate on a saved search + // const factory = new SearchEmbeddableFactory(plugins.uiActions.executeTriggerActions); + // plugins.embeddable.registerEmbeddableFactory(factory.type, factory); + } + + stop() {} +} diff --git a/src/legacy/core_plugins/kibana/public/discover/saved_searches/_saved_search.js b/src/legacy/core_plugins/kibana/public/discover/saved_searches/_saved_search.js index 3903dc0845450..9bbc5baf4fc22 100644 --- a/src/legacy/core_plugins/kibana/public/discover/saved_searches/_saved_search.js +++ b/src/legacy/core_plugins/kibana/public/discover/saved_searches/_saved_search.js @@ -17,10 +17,10 @@ * under the License. */ -import 'ui/notify'; -import { uiModules } from 'ui/modules'; import { createLegacyClass } from 'ui/utils/legacy_class'; -import { SavedObjectProvider } from 'ui/saved_objects/saved_object'; +import { getServices } from '../kibana_services'; + +const { uiModules, SavedObjectProvider } = getServices(); const module = uiModules.get('discover/saved_searches', []); @@ -40,7 +40,7 @@ module.factory('SavedSearch', function (Private) { columns: [], hits: 0, sort: [], - version: 1 + version: 1, }, }); @@ -55,7 +55,7 @@ module.factory('SavedSearch', function (Private) { hits: 'integer', columns: 'keyword', sort: 'keyword', - version: 'integer' + version: 'integer', }; // Order these fields to the top, the rest are alphabetical diff --git a/src/legacy/core_plugins/kibana/public/discover/saved_searches/saved_search_register.js b/src/legacy/core_plugins/kibana/public/discover/saved_searches/saved_search_register.js index 8460ccf923cf3..9554642c225fd 100644 --- a/src/legacy/core_plugins/kibana/public/discover/saved_searches/saved_search_register.js +++ b/src/legacy/core_plugins/kibana/public/discover/saved_searches/saved_search_register.js @@ -17,10 +17,10 @@ * under the License. */ -import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry'; +import { getServices } from '../kibana_services'; import './saved_searches'; -SavedObjectRegistryProvider.register((savedSearches) => { +getServices().SavedObjectRegistryProvider.register((savedSearches) => { return savedSearches; }); diff --git a/src/legacy/core_plugins/kibana/public/discover/top_nav/open_search_panel.js b/src/legacy/core_plugins/kibana/public/discover/top_nav/open_search_panel.js index f29ebe6a47141..0c3b52fbf0640 100644 --- a/src/legacy/core_plugins/kibana/public/discover/top_nav/open_search_panel.js +++ b/src/legacy/core_plugins/kibana/public/discover/top_nav/open_search_panel.js @@ -19,11 +19,9 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; import rison from 'rison-node'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; - import { EuiButton, EuiFlexGroup, @@ -34,6 +32,7 @@ import { EuiFlyoutBody, EuiTitle, } from '@elastic/eui'; +import { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; const SEARCH_OBJECT_TYPE = 'search'; diff --git a/src/legacy/core_plugins/kibana/public/discover/top_nav/open_search_panel.test.js b/src/legacy/core_plugins/kibana/public/discover/top_nav/open_search_panel.test.js index 2bec532110379..3531088e3847c 100644 --- a/src/legacy/core_plugins/kibana/public/discover/top_nav/open_search_panel.test.js +++ b/src/legacy/core_plugins/kibana/public/discover/top_nav/open_search_panel.test.js @@ -20,6 +20,14 @@ import React from 'react'; import { shallow } from 'enzyme'; +jest.mock('../kibana_services', () => { + return { + getServices: () => ({ + SavedObjectFinder: jest.fn() + }), + }; +}); + import { OpenSearchPanel, } from './open_search_panel'; diff --git a/src/legacy/core_plugins/kibana/public/discover/types.d.ts b/src/legacy/core_plugins/kibana/public/discover/types.d.ts index f285e94369893..7d8740243ec02 100644 --- a/src/legacy/core_plugins/kibana/public/discover/types.d.ts +++ b/src/legacy/core_plugins/kibana/public/discover/types.d.ts @@ -17,8 +17,8 @@ * under the License. */ -import { SearchSource } from 'ui/courier'; -import { SortOrder } from './doc_table/components/table_header/helpers'; +import { SearchSource } from './kibana_services'; +import { SortOrder } from './angular/doc_table/components/table_header/helpers'; export interface SavedSearch { readonly id: string; diff --git a/src/legacy/core_plugins/kibana/public/home/components/add_data.js b/src/legacy/core_plugins/kibana/public/home/components/add_data.js index f8c8e0ec8411f..0a3adfa430a45 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/add_data.js +++ b/src/legacy/core_plugins/kibana/public/home/components/add_data.js @@ -21,7 +21,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; -import chrome from 'ui/chrome'; +import { getServices } from '../kibana_services'; import { EuiButton, @@ -38,10 +38,9 @@ import { EuiFlexGrid, } from '@elastic/eui'; -/* istanbul ignore next */ -const basePath = chrome.getBasePath(); const AddDataUi = ({ apmUiEnabled, isNewKibanaInstance, intl, mlEnabled }) => { + const basePath = getServices().getBasePath(); const renderCards = () => { const apmData = { title: intl.formatMessage({ diff --git a/src/legacy/core_plugins/kibana/public/home/components/add_data.test.js b/src/legacy/core_plugins/kibana/public/home/components/add_data.test.js index d7c8e9daa99da..07f415cfcb1c9 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/add_data.test.js +++ b/src/legacy/core_plugins/kibana/public/home/components/add_data.test.js @@ -20,15 +20,20 @@ import React from 'react'; import { AddData } from './add_data'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; -import chrome from 'ui/chrome'; +import { getServices } from '../kibana_services'; -jest.mock( - 'ui/chrome', - () => ({ +jest.mock('../kibana_services', () =>{ + const mock = { getBasePath: jest.fn(() => 'path'), - }), - { virtual: true } -); + }; + return { + getServices: () => mock, + }; +}); + +beforeEach(() => { + jest.clearAllMocks(); +}); test('render', () => { const component = shallowWithIntl( { isNewKibanaInstance={false} />); expect(component).toMatchSnapshot(); // eslint-disable-line - expect(chrome.getBasePath).toHaveBeenCalledTimes(1); + expect(getServices().getBasePath).toHaveBeenCalledTimes(1); }); test('mlEnabled', () => { @@ -47,7 +52,7 @@ test('mlEnabled', () => { isNewKibanaInstance={false} />); expect(component).toMatchSnapshot(); // eslint-disable-line - expect(chrome.getBasePath).toHaveBeenCalledTimes(1); + expect(getServices().getBasePath).toHaveBeenCalledTimes(1); }); test('apmUiEnabled', () => { @@ -57,7 +62,7 @@ test('apmUiEnabled', () => { isNewKibanaInstance={false} />); expect(component).toMatchSnapshot(); // eslint-disable-line - expect(chrome.getBasePath).toHaveBeenCalledTimes(1); + expect(getServices().getBasePath).toHaveBeenCalledTimes(1); }); test('isNewKibanaInstance', () => { @@ -67,5 +72,5 @@ test('isNewKibanaInstance', () => { isNewKibanaInstance={true} />); expect(component).toMatchSnapshot(); // eslint-disable-line - expect(chrome.getBasePath).toHaveBeenCalledTimes(1); + expect(getServices().getBasePath).toHaveBeenCalledTimes(1); }); diff --git a/src/legacy/core_plugins/kibana/public/home/components/home.js b/src/legacy/core_plugins/kibana/public/home/components/home.js index 6a8dff2ad4fa7..e4c7de9b495a0 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/home.js +++ b/src/legacy/core_plugins/kibana/public/home/components/home.js @@ -22,7 +22,6 @@ import PropTypes from 'prop-types'; import { Synopsis } from './synopsis'; import { AddData } from './add_data'; import { FormattedMessage } from '@kbn/i18n/react'; -import chrome from 'ui/chrome'; import { EuiButton, @@ -40,6 +39,7 @@ import { import { Welcome } from './welcome'; import { FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; +import { getServices } from '../kibana_services'; const KEY_ENABLE_WELCOME = 'home:welcome:show'; @@ -47,7 +47,10 @@ export class Home extends Component { constructor(props) { super(props); - const isWelcomeEnabled = !(chrome.getInjected('disableWelcomeScreen') || props.localStorage.getItem(KEY_ENABLE_WELCOME) === 'false'); + const isWelcomeEnabled = !( + getServices().getInjected('disableWelcomeScreen') || + props.localStorage.getItem(KEY_ENABLE_WELCOME) === 'false' + ); this.state = { // If welcome is enabled, we wait for loading to complete diff --git a/src/legacy/core_plugins/kibana/public/home/components/home.test.js b/src/legacy/core_plugins/kibana/public/home/components/home.test.js index aa520ba2ed5f9..c21c6fa3d98a5 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/home.test.js +++ b/src/legacy/core_plugins/kibana/public/home/components/home.test.js @@ -25,6 +25,13 @@ import { shallow } from 'enzyme'; import { Home } from './home'; import { FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; +jest.mock('../kibana_services', () =>({ + getServices: () => ({ + getBasePath: () => 'path', + getInjected: () => '' + }) +})); + describe('home', () => { let defaultProps; diff --git a/src/legacy/core_plugins/kibana/public/home/components/home.test.mocks.ts b/src/legacy/core_plugins/kibana/public/home/components/home.test.mocks.ts index 621c058c803db..cd7bc82fe3345 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/home.test.mocks.ts +++ b/src/legacy/core_plugins/kibana/public/home/components/home.test.mocks.ts @@ -17,7 +17,12 @@ * under the License. */ -import { notificationServiceMock, overlayServiceMock } from '../../../../../../core/public/mocks'; +import { + notificationServiceMock, + overlayServiceMock, + httpServiceMock, + injectedMetadataServiceMock, +} from '../../../../../../core/public/mocks'; jest.doMock('ui/new_platform', () => { return { @@ -29,22 +34,9 @@ jest.doMock('ui/new_platform', () => { npStart: { core: { overlays: overlayServiceMock.createStartContract(), + http: httpServiceMock.createStartContract({ basePath: 'path' }), + injectedMetadata: injectedMetadataServiceMock.createStartContract(), }, }, }; }); - -jest.doMock( - 'ui/chrome', - () => ({ - getBasePath: jest.fn(() => 'path'), - getInjected: jest.fn(() => ''), - }), - { virtual: true } -); - -jest.doMock('ui/capabilities', () => ({ - catalogue: {}, - management: {}, - navLinks: {}, -})); diff --git a/src/legacy/core_plugins/kibana/public/home/components/home_app.js b/src/legacy/core_plugins/kibana/public/home/components/home_app.js index 9aa44863f6d70..005d4bdb0a99e 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/home_app.js +++ b/src/legacy/core_plugins/kibana/public/home/components/home_app.js @@ -26,23 +26,32 @@ import { Tutorial } from './tutorial/tutorial'; import { HashRouter as Router, Switch, - Route + Route, } from 'react-router-dom'; import { getTutorial } from '../load_tutorials'; import { replaceTemplateStrings } from './tutorial/replace_template_strings'; -import { telemetryOptInProvider, shouldShowTelemetryOptIn } from '../kibana_services'; -import chrome from 'ui/chrome'; +import { + getServices +} from '../kibana_services'; export function HomeApp({ directories }) { - const isCloudEnabled = chrome.getInjected('isCloudEnabled', false); - const apmUiEnabled = chrome.getInjected('apmUiEnabled', true); - const mlEnabled = chrome.getInjected('mlEnabled', false); - const savedObjectsClient = chrome.getSavedObjectsClient(); + const { + telemetryOptInProvider, + shouldShowTelemetryOptIn, + getInjected, + savedObjectsClient, + getBasePath, + addBasePath, + } = getServices(); + + const isCloudEnabled = getInjected('isCloudEnabled', false); + const apmUiEnabled = getInjected('apmUiEnabled', true); + const mlEnabled = getInjected('mlEnabled', false); const renderTutorialDirectory = (props) => { return ( @@ -52,7 +61,7 @@ export function HomeApp({ directories }) { const renderTutorial = (props) => { return ( @@ -85,13 +94,13 @@ export function HomeApp({ directories }) { path="/home" > { - return IS_DARK_THEME && sampleDataSet.darkPreviewImagePath ? sampleDataSet.darkPreviewImagePath : sampleDataSet.previewImagePath; + return getServices().uiSettings.get('theme:darkMode') && sampleDataSet.darkPreviewImagePath + ? sampleDataSet.darkPreviewImagePath + : sampleDataSet.previewImagePath; } render() { diff --git a/src/legacy/core_plugins/kibana/public/home/components/sample_data_view_data_button.js b/src/legacy/core_plugins/kibana/public/home/components/sample_data_view_data_button.js index 1df99e9b03d11..89d4909b0c66f 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/sample_data_view_data_button.js +++ b/src/legacy/core_plugins/kibana/public/home/components/sample_data_view_data_button.js @@ -27,9 +27,10 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import chrome from 'ui/chrome'; +import { getServices } from '../kibana_services'; export class SampleDataViewDataButton extends React.Component { + addBasePath = getServices().addBasePath; state = { isPopoverOpen: false @@ -56,7 +57,7 @@ export class SampleDataViewDataButton extends React.Component { datasetName: this.props.name, }, }); - const dashboardPath = chrome.addBasePath(`/app/kibana#/dashboard/${this.props.overviewDashboard}`); + const dashboardPath = this.addBasePath(`/app/kibana#/dashboard/${this.props.overviewDashboard}`); if (this.props.appLinks.length === 0) { return ( @@ -79,7 +80,7 @@ export class SampleDataViewDataButton extends React.Component { size="m" /> ), - href: chrome.addBasePath(path) + href: this.addBasePath(path) }; }); const panels = [ diff --git a/src/legacy/core_plugins/kibana/public/home/components/sample_data_view_data_button.test.js b/src/legacy/core_plugins/kibana/public/home/components/sample_data_view_data_button.test.js index b0551341965fa..cc515993ac061 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/sample_data_view_data_button.test.js +++ b/src/legacy/core_plugins/kibana/public/home/components/sample_data_view_data_button.test.js @@ -17,19 +17,18 @@ * under the License. */ -jest.mock('ui/chrome', () => { - return { - addBasePath: (path) => { - return `root${path}`; - }, - }; -}); import React from 'react'; import { shallow } from 'enzyme'; import { SampleDataViewDataButton } from './sample_data_view_data_button'; +jest.mock('../kibana_services', () =>({ + getServices: () =>({ + addBasePath: path => `root${path}` + }) +})); + test('should render simple button when appLinks is empty', () => { const component = shallow(({ + getServices: () =>({ + getBasePath: jest.fn(() => 'path'), + chrome: { + setBreadcrumbs: () => {} + } + }) +})); jest.mock('../../../../../kibana_react/public', () => { return { Markdown: () =>
, diff --git a/src/legacy/core_plugins/kibana/public/home/components/tutorial_directory.js b/src/legacy/core_plugins/kibana/public/home/components/tutorial_directory.js index eae549f8a6ac0..0c537c8e9ae8a 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/tutorial_directory.js +++ b/src/legacy/core_plugins/kibana/public/home/components/tutorial_directory.js @@ -22,7 +22,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Synopsis } from './synopsis'; import { SampleDataSetCards } from './sample_data_set_cards'; -import chrome from 'ui/chrome'; +import { getServices } from '../kibana_services'; import { EuiPage, @@ -112,7 +112,7 @@ class TutorialDirectoryUi extends React.Component { async componentDidMount() { this._isMounted = true; - chrome.breadcrumbs.set([ + getServices().chrome.setBreadcrumbs([ { text: homeTitle, href: '#/home', diff --git a/src/legacy/core_plugins/kibana/public/home/components/welcome.tsx b/src/legacy/core_plugins/kibana/public/home/components/welcome.tsx index 8869819290263..afe43a23e18cb 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/welcome.tsx +++ b/src/legacy/core_plugins/kibana/public/home/components/welcome.tsx @@ -33,15 +33,11 @@ import { EuiIcon, EuiPortal, } from '@elastic/eui'; -// @ts-ignore -import { banners } from 'ui/notify'; - import { FormattedMessage } from '@kbn/i18n/react'; -import chrome from 'ui/chrome'; +import { getServices } from '../kibana_services'; + import { SampleDataCard } from './sample_data'; import { TelemetryOptInCard } from './telemetry_opt_in'; -// @ts-ignore -import { trackUiMetric, METRIC_TYPE } from '../kibana_services'; interface Props { urlBasePath: string; @@ -51,6 +47,7 @@ interface Props { getTelemetryBannerId: () => string; shouldShowTelemetryOptIn: boolean; } + interface State { step: number; } @@ -59,6 +56,7 @@ interface State { * Shows a full-screen welcome page that gives helpful quick links to beginners. */ export class Welcome extends React.PureComponent { + private services = getServices(); public readonly state: State = { step: 0, }; @@ -70,31 +68,35 @@ export class Welcome extends React.PureComponent { }; private redirecToSampleData() { - const path = chrome.addBasePath('#/home/tutorial_directory/sampleData'); + const path = this.services.addBasePath('#/home/tutorial_directory/sampleData'); window.location.href = path; } + private async handleTelemetrySelection(confirm: boolean) { const metricName = `telemetryOptIn${confirm ? 'Confirm' : 'Decline'}`; - trackUiMetric(METRIC_TYPE.CLICK, metricName); + this.services.trackUiMetric(this.services.METRIC_TYPE.CLICK, metricName); await this.props.setOptIn(confirm); const bannerId = this.props.getTelemetryBannerId(); - banners.remove(bannerId); + this.services.banners.remove(bannerId); this.setState(() => ({ step: 1 })); } private onSampleDataDecline = () => { - trackUiMetric(METRIC_TYPE.CLICK, 'sampleDataDecline'); + this.services.trackUiMetric(this.services.METRIC_TYPE.CLICK, 'sampleDataDecline'); this.props.onSkip(); }; private onSampleDataConfirm = () => { - trackUiMetric(METRIC_TYPE.CLICK, 'sampleDataConfirm'); + this.services.trackUiMetric(this.services.METRIC_TYPE.CLICK, 'sampleDataConfirm'); this.redirecToSampleData(); }; componentDidMount() { - trackUiMetric(METRIC_TYPE.LOADED, 'welcomeScreenMount'); + this.services.trackUiMetric(this.services.METRIC_TYPE.LOADED, 'welcomeScreenMount'); if (this.props.shouldShowTelemetryOptIn) { - trackUiMetric(METRIC_TYPE.COUNT, 'welcomeScreenWithTelemetryOptIn'); + this.services.trackUiMetric( + this.services.METRIC_TYPE.COUNT, + 'welcomeScreenWithTelemetryOptIn' + ); } document.addEventListener('keydown', this.hideOnEsc); } diff --git a/src/legacy/core_plugins/kibana/public/home/index.js b/src/legacy/core_plugins/kibana/public/home/index.js index 8233df680edfd..01f94b8ee4368 100644 --- a/src/legacy/core_plugins/kibana/public/home/index.js +++ b/src/legacy/core_plugins/kibana/public/home/index.js @@ -17,17 +17,14 @@ * under the License. */ -import chrome from 'ui/chrome'; -import routes from 'ui/routes'; +import { getServices } from './kibana_services'; import template from './home_ng_wrapper.html'; -import { FeatureCatalogueRegistryProvider } from 'ui/registry/feature_catalogue'; -import { wrapInI18nContext } from 'ui/i18n'; -import { uiModules } from 'ui/modules'; import { HomeApp } from './components/home_app'; import { i18n } from '@kbn/i18n'; -import { npStart } from 'ui/new_platform'; + +const { wrapInI18nContext, uiRoutes, uiModules } = getServices(); const app = uiModules.get('apps/home', []); app.directive('homeApp', function (reactDirective) { @@ -39,10 +36,14 @@ const homeTitle = i18n.translate('kbn.home.breadcrumbs.homeTitle', { defaultMess function getRoute() { return { template, - controller($scope, Private) { - $scope.directories = Private(FeatureCatalogueRegistryProvider).inTitleOrder; - $scope.recentlyAccessed = npStart.core.chrome.recentlyAccessed.get().map(item => { - item.link = chrome.addBasePath(item.link); + resolve: { + directories: () => getServices().getFeatureCatalogueEntries() + }, + controller($scope, $route) { + const { chrome, addBasePath } = getServices(); + $scope.directories = $route.current.locals.directories; + $scope.recentlyAccessed = chrome.recentlyAccessed.get().map(item => { + item.link = addBasePath(item.link); return item; }); }, @@ -54,7 +55,7 @@ function getRoute() { // All routing will be handled inside HomeApp via react, we just need to make sure angular doesn't // redirect us to the default page by encountering a url it isn't marked as being able to handle. -routes.when('/home', getRoute()); -routes.when('/home/feature_directory', getRoute()); -routes.when('/home/tutorial_directory/:tab?', getRoute()); -routes.when('/home/tutorial/:id', getRoute()); +uiRoutes.when('/home', getRoute()); +uiRoutes.when('/home/feature_directory', getRoute()); +uiRoutes.when('/home/tutorial_directory/:tab?', getRoute()); +uiRoutes.when('/home/tutorial/:id', getRoute()); diff --git a/src/legacy/core_plugins/kibana/public/home/kibana_services.js b/src/legacy/core_plugins/kibana/public/home/kibana_services.js deleted file mode 100644 index 792c5e09435a4..0000000000000 --- a/src/legacy/core_plugins/kibana/public/home/kibana_services.js +++ /dev/null @@ -1,40 +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 { uiModules } from 'ui/modules'; -import { npStart } from 'ui/new_platform'; -import { createUiStatsReporter, METRIC_TYPE } from '../../../ui_metric/public'; -import { TelemetryOptInProvider } from '../../../telemetry/public/services'; - -export let indexPatternService; -export let shouldShowTelemetryOptIn; -export let telemetryOptInProvider; - -export const trackUiMetric = createUiStatsReporter('Kibana_home'); -export { METRIC_TYPE }; - -uiModules.get('kibana').run(($injector) => { - const telemetryEnabled = npStart.core.injectedMetadata.getInjectedVar('telemetryEnabled'); - const telemetryBanner = npStart.core.injectedMetadata.getInjectedVar('telemetryBanner'); - const Private = $injector.get('Private'); - - telemetryOptInProvider = Private(TelemetryOptInProvider); - shouldShowTelemetryOptIn = telemetryEnabled && telemetryBanner && !telemetryOptInProvider.getOptIn(); - indexPatternService = $injector.get('indexPatterns'); -}); diff --git a/src/legacy/core_plugins/kibana/public/home/kibana_services.ts b/src/legacy/core_plugins/kibana/public/home/kibana_services.ts new file mode 100644 index 0000000000000..b9f2ae1cfa7e8 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/home/kibana_services.ts @@ -0,0 +1,85 @@ +/* + * 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. + */ + +// @ts-ignore +import { toastNotifications, banners } from 'ui/notify'; +import { kfetch } from 'ui/kfetch'; +import chrome from 'ui/chrome'; + +import { wrapInI18nContext } from 'ui/i18n'; + +// @ts-ignore +import { uiModules as modules } from 'ui/modules'; +import routes from 'ui/routes'; +import { npSetup, npStart } from 'ui/new_platform'; +import { IPrivate } from 'ui/private'; +import { FeatureCatalogueRegistryProvider } from 'ui/registry/feature_catalogue'; +import { createUiStatsReporter, METRIC_TYPE } from '../../../ui_metric/public'; +import { TelemetryOptInProvider } from '../../../telemetry/public/services'; +import { start as data } from '../../../data/public/legacy'; + +let shouldShowTelemetryOptIn: boolean; +let telemetryOptInProvider: any; + +export function getServices() { + return { + getInjected: npStart.core.injectedMetadata.getInjectedVar, + metadata: npStart.core.injectedMetadata.getLegacyMetadata(), + docLinks: npStart.core.docLinks, + + uiRoutes: routes, + uiModules: modules, + + savedObjectsClient: npStart.core.savedObjects.client, + chrome: npStart.core.chrome, + uiSettings: npStart.core.uiSettings, + addBasePath: npStart.core.http.basePath.prepend, + getBasePath: npStart.core.http.basePath.get, + + indexPatternService: data.indexPatterns.indexPatterns, + shouldShowTelemetryOptIn, + telemetryOptInProvider, + getFeatureCatalogueEntries: async () => { + const injector = await chrome.dangerouslyGetActiveInjector(); + const Private = injector.get('Private'); + // Merge legacy registry with new registry + (Private(FeatureCatalogueRegistryProvider as any) as any).inTitleOrder.map( + npSetup.plugins.feature_catalogue.register + ); + return npStart.plugins.feature_catalogue.get(); + }, + + trackUiMetric: createUiStatsReporter('Kibana_home'), + METRIC_TYPE, + + toastNotifications, + banners, + kfetch, + wrapInI18nContext, + }; +} + +modules.get('kibana').run((Private: IPrivate) => { + const telemetryEnabled = npStart.core.injectedMetadata.getInjectedVar('telemetryEnabled'); + const telemetryBanner = npStart.core.injectedMetadata.getInjectedVar('telemetryBanner'); + + telemetryOptInProvider = Private(TelemetryOptInProvider); + shouldShowTelemetryOptIn = + telemetryEnabled && telemetryBanner && !telemetryOptInProvider.getOptIn(); +}); diff --git a/src/legacy/core_plugins/kibana/public/home/load_tutorials.js b/src/legacy/core_plugins/kibana/public/home/load_tutorials.js index d6b264154d424..a6f19bc166dc7 100644 --- a/src/legacy/core_plugins/kibana/public/home/load_tutorials.js +++ b/src/legacy/core_plugins/kibana/public/home/load_tutorials.js @@ -18,11 +18,10 @@ */ import _ from 'lodash'; -import chrome from 'ui/chrome'; +import { getServices } from './kibana_services'; import { i18n } from '@kbn/i18n'; -import { toastNotifications } from 'ui/notify'; -const baseUrl = chrome.addBasePath('/api/kibana/home/tutorials'); +const baseUrl = getServices().addBasePath('/api/kibana/home/tutorials'); const headers = new Headers(); headers.append('Accept', 'application/json'); headers.append('Content-Type', 'application/json'); @@ -47,7 +46,7 @@ async function loadTutorials() { tutorials = await response.json(); tutorialsLoaded = true; } catch(err) { - toastNotifications.addDanger({ + getServices().toastNotifications.addDanger({ title: i18n.translate('kbn.home.loadTutorials.unableToLoadErrorMessage', { defaultMessage: 'Unable to load tutorials' } ), diff --git a/src/legacy/core_plugins/kibana/public/home/sample_data_client.js b/src/legacy/core_plugins/kibana/public/home/sample_data_client.js index da46b3e16c093..9411373004c25 100644 --- a/src/legacy/core_plugins/kibana/public/home/sample_data_client.js +++ b/src/legacy/core_plugins/kibana/public/home/sample_data_client.js @@ -17,36 +17,36 @@ * under the License. */ -import { kfetch } from 'ui/kfetch'; -import chrome from 'ui/chrome'; -import { indexPatternService } from './kibana_services'; +import { getServices } from './kibana_services'; const sampleDataUrl = '/api/sample_data'; function clearIndexPatternsCache() { - indexPatternService.clearCache(); + getServices().indexPatternService.clearCache(); } export async function listSampleDataSets() { - return await kfetch({ method: 'GET', pathname: sampleDataUrl }); + return await getServices().kfetch({ method: 'GET', pathname: sampleDataUrl }); } export async function installSampleDataSet(id, sampleDataDefaultIndex) { - await kfetch({ method: 'POST', pathname: `${sampleDataUrl}/${id}` }); + await getServices().kfetch({ method: 'POST', pathname: `${sampleDataUrl}/${id}` }); - if (chrome.getUiSettingsClient().isDefault('defaultIndex')) { - chrome.getUiSettingsClient().set('defaultIndex', sampleDataDefaultIndex); + if (getServices().uiSettings.isDefault('defaultIndex')) { + getServices().uiSettings.set('defaultIndex', sampleDataDefaultIndex); } clearIndexPatternsCache(); } export async function uninstallSampleDataSet(id, sampleDataDefaultIndex) { - await kfetch({ method: 'DELETE', pathname: `${sampleDataUrl}/${id}` }); + await getServices().kfetch({ method: 'DELETE', pathname: `${sampleDataUrl}/${id}` }); - if (!chrome.getUiSettingsClient().isDefault('defaultIndex') - && chrome.getUiSettingsClient().get('defaultIndex') === sampleDataDefaultIndex) { - chrome.getUiSettingsClient().set('defaultIndex', null); + const uiSettings = getServices().uiSettings; + + if (!uiSettings.isDefault('defaultIndex') + && uiSettings.get('defaultIndex') === sampleDataDefaultIndex) { + uiSettings.set('defaultIndex', null); } clearIndexPatternsCache(); diff --git a/src/legacy/core_plugins/kibana/public/home/tutorial_resources/consul_metrics/screenshot.png b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/consul_metrics/screenshot.png new file mode 100644 index 0000000000000..90aaa7477e8eb Binary files /dev/null and b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/consul_metrics/screenshot.png differ diff --git a/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/consul.svg b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/consul.svg new file mode 100644 index 0000000000000..28bbadd24c8a6 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/consul.svg @@ -0,0 +1 @@ +Asset 1 \ No newline at end of file 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 b5e112a489ce4..8aedc6f7332dc 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 @@ -19,7 +19,7 @@ import { SavedObject, SavedObjectAttributes } from 'src/core/server'; import { collectReferencesDeep } from './collect_references_deep'; -import { SavedObjectsClientMock } from '../../../../../../core/server/mocks'; +import { savedObjectsClientMock } from '../../../../../../core/server/mocks'; const data: Array> = [ { @@ -102,7 +102,7 @@ const data: Array> = [ ]; test('collects dashboard and all dependencies', async () => { - const savedObjectClient = SavedObjectsClientMock.create(); + const savedObjectClient = savedObjectsClientMock.create(); savedObjectClient.bulkGet.mockImplementation(objects => { if (!objects) { throw new Error('Invalid test data'); diff --git a/src/legacy/core_plugins/kibana/server/tutorials/consul_metrics/index.js b/src/legacy/core_plugins/kibana/server/tutorials/consul_metrics/index.js new file mode 100644 index 0000000000000..8823fe5ee5067 --- /dev/null +++ b/src/legacy/core_plugins/kibana/server/tutorials/consul_metrics/index.js @@ -0,0 +1,63 @@ +/* + * 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 { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { onPremInstructions, cloudInstructions, onPremCloudInstructions } from '../../../common/tutorials/metricbeat_instructions'; + +export function consulMetricsSpecProvider(server, context) { + const moduleName = 'consul'; + return { + id: 'consulMetrics', + name: i18n.translate('kbn.server.tutorials.consulMetrics.nameTitle', { + defaultMessage: 'Consul metrics', + }), + category: TUTORIAL_CATEGORY.METRICS, + shortDescription: i18n.translate('kbn.server.tutorials.consulMetrics.shortDescription', { + defaultMessage: 'Fetch monitoring metrics from the Consul server.', + }), + longDescription: i18n.translate('kbn.server.tutorials.consulMetrics.longDescription', { + defaultMessage: 'The `consul` Metricbeat module fetches monitoring metrics from Consul. \ +[Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.metricbeat}/metricbeat-module-consul.html', + }, + }), + euiIconType: '/plugins/kibana/home/tutorial_resources/logos/consul.svg', + artifacts: { + dashboards: [ + { + id: '496910f0-b952-11e9-a579-f5c0a5d81340', + linkLabel: i18n.translate('kbn.server.tutorials.consulMetrics.artifacts.dashboards.linkLabel', { + defaultMessage: 'Consul metrics dashboard', + }), + isOverview: true + } + ], + exportedFields: { + documentationUrl: '{config.docs.beats.metricbeat}/exported-fields-consul.html' + } + }, + completionTimeMinutes: 10, + previewImagePath: '/plugins/kibana/home/tutorial_resources/consul_metrics/screenshot.png', + onPrem: onPremInstructions(moduleName, null, null, null, context), + elasticCloud: cloudInstructions(moduleName), + onPremElasticCloud: onPremCloudInstructions(moduleName) + }; +} diff --git a/src/legacy/core_plugins/kibana/server/tutorials/register.js b/src/legacy/core_plugins/kibana/server/tutorials/register.js index 0299d00856c64..70bc0f4d3c9eb 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/register.js +++ b/src/legacy/core_plugins/kibana/server/tutorials/register.js @@ -77,6 +77,7 @@ import { ciscoLogsSpecProvider } from './cisco_logs'; import { envoyproxyLogsSpecProvider } from './envoyproxy_logs'; import { couchdbMetricsSpecProvider } from './couchdb_metrics'; import { emsBoundariesSpecProvider } from './ems'; +import { consulMetricsSpecProvider } from './consul_metrics'; export function registerTutorials(server) { server.registerTutorial(systemLogsSpecProvider); @@ -139,4 +140,5 @@ export function registerTutorials(server) { server.registerTutorial(envoyproxyLogsSpecProvider); server.registerTutorial(couchdbMetricsSpecProvider); server.registerTutorial(emsBoundariesSpecProvider); + server.registerTutorial(consulMetricsSpecProvider); } diff --git a/src/legacy/core_plugins/kibana_react/public/index.scss b/src/legacy/core_plugins/kibana_react/public/index.scss index 14922ca8ee8eb..14b4687c459e1 100644 --- a/src/legacy/core_plugins/kibana_react/public/index.scss +++ b/src/legacy/core_plugins/kibana_react/public/index.scss @@ -1,5 +1,3 @@ @import 'src/legacy/ui/public/styles/styling_constants'; -@import './top_nav_menu/index'; - @import './markdown/index'; diff --git a/src/legacy/core_plugins/kibana_react/public/index.ts b/src/legacy/core_plugins/kibana_react/public/index.ts index 2497671ca98d2..7e68b6c3886ff 100644 --- a/src/legacy/core_plugins/kibana_react/public/index.ts +++ b/src/legacy/core_plugins/kibana_react/public/index.ts @@ -23,6 +23,4 @@ // of the ExpressionExectorService /** @public types */ -export { TopNavMenu, TopNavMenuData } from './top_nav_menu'; - export { Markdown, MarkdownSimple } from './markdown'; diff --git a/src/legacy/ui/ui_settings/routes/set_many.ts b/src/legacy/core_plugins/navigation/index.ts similarity index 55% rename from src/legacy/ui/ui_settings/routes/set_many.ts rename to src/legacy/core_plugins/navigation/index.ts index 18b1046417fec..32d5f040760c6 100644 --- a/src/legacy/ui/ui_settings/routes/set_many.ts +++ b/src/legacy/core_plugins/navigation/index.ts @@ -16,35 +16,27 @@ * specific language governing permissions and limitations * under the License. */ -import { Legacy } from 'kibana'; -import Joi from 'joi'; -async function handleRequest(request: Legacy.Request) { - const { changes } = request.payload as any; - const uiSettings = request.getUiSettingsService(); +import { resolve } from 'path'; +import { Legacy } from '../../../../kibana'; - await uiSettings.setMany(changes); - - return { - settings: await uiSettings.getUserProvided(), - }; -} - -export const setManyRoute = { - path: '/api/kibana/settings', - method: 'POST', - config: { - validate: { - payload: Joi.object() - .keys({ - changes: Joi.object() - .unknown(true) - .required(), - }) - .required(), +// eslint-disable-next-line import/no-default-export +export default function NavigationPlugin(kibana: any) { + const config: Legacy.PluginSpecOptions = { + id: 'navigation', + require: [], + publicDir: resolve(__dirname, 'public'), + config: (Joi: any) => { + return Joi.object({ + enabled: Joi.boolean().default(true), + }).default(); }, - handler(request: Legacy.Request) { - return handleRequest(request); + init: (server: Legacy.Server) => ({}), + uiExports: { + injectDefaultVars: () => ({}), + styleSheetPaths: resolve(__dirname, 'public/index.scss'), }, - }, -}; + }; + + return new kibana.Plugin(config); +} diff --git a/src/legacy/core_plugins/navigation/package.json b/src/legacy/core_plugins/navigation/package.json new file mode 100644 index 0000000000000..8fddb8e6aeced --- /dev/null +++ b/src/legacy/core_plugins/navigation/package.json @@ -0,0 +1,4 @@ +{ + "name": "navigation", + "version": "kibana" +} diff --git a/src/legacy/core_plugins/navigation/public/index.scss b/src/legacy/core_plugins/navigation/public/index.scss new file mode 100644 index 0000000000000..4c969b1a37e8c --- /dev/null +++ b/src/legacy/core_plugins/navigation/public/index.scss @@ -0,0 +1,3 @@ +@import 'src/legacy/ui/public/styles/styling_constants'; + +@import './top_nav_menu/index'; diff --git a/src/legacy/core_plugins/navigation/public/index.ts b/src/legacy/core_plugins/navigation/public/index.ts new file mode 100644 index 0000000000000..439405cc90b57 --- /dev/null +++ b/src/legacy/core_plugins/navigation/public/index.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. + */ + +// TODO these are imports from the old plugin world. +// Once the new platform is ready, they can get removed +// and handled by the platform itself in the setup method +// of the ExpressionExectorService + +/** @public types */ +export { TopNavMenu, TopNavMenuData } from './top_nav_menu'; +export { NavigationSetup, NavigationStart } from './plugin'; + +import { NavigationPlugin as Plugin } from './plugin'; +export function plugin() { + return new Plugin(); +} diff --git a/src/legacy/core_plugins/navigation/public/legacy.ts b/src/legacy/core_plugins/navigation/public/legacy.ts new file mode 100644 index 0000000000000..1783514e6fbdc --- /dev/null +++ b/src/legacy/core_plugins/navigation/public/legacy.ts @@ -0,0 +1,30 @@ +/* + * 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, npStart } from 'ui/new_platform'; +import { start as dataShim } from '../../data/public/legacy'; +import { plugin } from '.'; + +const navPlugin = plugin(); + +export const setup = navPlugin.setup(npSetup.core); + +export const start = navPlugin.start(npStart.core, { + data: dataShim, +}); diff --git a/src/legacy/core_plugins/navigation/public/plugin.ts b/src/legacy/core_plugins/navigation/public/plugin.ts new file mode 100644 index 0000000000000..65a0902dec986 --- /dev/null +++ b/src/legacy/core_plugins/navigation/public/plugin.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 { CoreSetup, CoreStart, Plugin } from 'kibana/public'; +import { TopNavMenuExtensionsRegistry, TopNavMenuExtensionsRegistrySetup } from './top_nav_menu'; +import { createTopNav } from './top_nav_menu/create_top_nav_menu'; +import { TopNavMenuProps } from './top_nav_menu/top_nav_menu'; +import { DataStart } from '../../data/public'; + +/** + * Interface for this plugin's returned `setup` contract. + * + * @public + */ +export interface NavigationSetup { + registerMenuItem: TopNavMenuExtensionsRegistrySetup['register']; +} + +/** + * Interface for this plugin's returned `start` contract. + * + * @public + */ +export interface NavigationStart { + ui: { + TopNavMenu: React.ComponentType; + }; +} + +export interface NavigationPluginStartDependencies { + data: DataStart; +} + +export class NavigationPlugin implements Plugin { + private readonly topNavMenuExtensionsRegistry: TopNavMenuExtensionsRegistry = new TopNavMenuExtensionsRegistry(); + + public setup(core: CoreSetup): NavigationSetup { + return { + registerMenuItem: this.topNavMenuExtensionsRegistry.register.bind( + this.topNavMenuExtensionsRegistry + ), + }; + } + + public start(core: CoreStart, { data }: NavigationPluginStartDependencies): NavigationStart { + const extensions = this.topNavMenuExtensionsRegistry.getAll(); + + return { + ui: { + TopNavMenu: createTopNav(data, extensions), + }, + }; + } + + public stop() { + this.topNavMenuExtensionsRegistry.clear(); + } +} diff --git a/src/legacy/core_plugins/kibana_react/public/top_nav_menu/_index.scss b/src/legacy/core_plugins/navigation/public/top_nav_menu/_index.scss similarity index 100% rename from src/legacy/core_plugins/kibana_react/public/top_nav_menu/_index.scss rename to src/legacy/core_plugins/navigation/public/top_nav_menu/_index.scss diff --git a/src/legacy/ui/ui_settings/routes/delete.ts b/src/legacy/core_plugins/navigation/public/top_nav_menu/create_top_nav_menu.tsx similarity index 64% rename from src/legacy/ui/ui_settings/routes/delete.ts rename to src/legacy/core_plugins/navigation/public/top_nav_menu/create_top_nav_menu.tsx index 7825204e6b99b..fa0e5fcac7407 100644 --- a/src/legacy/ui/ui_settings/routes/delete.ts +++ b/src/legacy/core_plugins/navigation/public/top_nav_menu/create_top_nav_menu.tsx @@ -16,22 +16,16 @@ * specific language governing permissions and limitations * under the License. */ -import { Legacy } from 'kibana'; -async function handleRequest(request: Legacy.Request) { - const { key } = request.params; - const uiSettings = request.getUiSettingsService(); +import React from 'react'; +import { TopNavMenuProps, TopNavMenu } from './top_nav_menu'; +import { TopNavMenuData } from './top_nav_menu_data'; +import { DataStart } from '../../../../core_plugins/data/public'; - await uiSettings.remove(key); - return { - settings: await uiSettings.getUserProvided(), +export function createTopNav(data: DataStart, extraConfig: TopNavMenuData[]) { + return (props: TopNavMenuProps) => { + const config = (props.config || []).concat(extraConfig); + + return ; }; } - -export const deleteRoute = { - path: '/api/kibana/settings/{key}', - method: 'DELETE', - handler: async (request: Legacy.Request) => { - return await handleRequest(request); - }, -}; diff --git a/src/legacy/core_plugins/kibana_react/public/top_nav_menu/index.ts b/src/legacy/core_plugins/navigation/public/top_nav_menu/index.ts similarity index 94% rename from src/legacy/core_plugins/kibana_react/public/top_nav_menu/index.ts rename to src/legacy/core_plugins/navigation/public/top_nav_menu/index.ts index b45baaf0e4711..dd139bbd6410f 100644 --- a/src/legacy/core_plugins/kibana_react/public/top_nav_menu/index.ts +++ b/src/legacy/core_plugins/navigation/public/top_nav_menu/index.ts @@ -19,3 +19,4 @@ export { TopNavMenu } from './top_nav_menu'; export { TopNavMenuData } from './top_nav_menu_data'; +export * from './top_nav_menu_extensions_registry'; diff --git a/src/legacy/core_plugins/kibana_react/public/top_nav_menu/top_nav_menu.test.tsx b/src/legacy/core_plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx similarity index 88% rename from src/legacy/core_plugins/kibana_react/public/top_nav_menu/top_nav_menu.test.tsx rename to src/legacy/core_plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx index 21c5cef4ae925..803119bdac119 100644 --- a/src/legacy/core_plugins/kibana_react/public/top_nav_menu/top_nav_menu.test.tsx +++ b/src/legacy/core_plugins/navigation/public/top_nav_menu/top_nav_menu.test.tsx @@ -27,21 +27,11 @@ const timefilterSetupMock = timefilterServiceMock.createSetupContract(); jest.mock('ui/new_platform'); -jest.mock('../../../../../../src/legacy/core_plugins/data/public/legacy', () => ({ - start: { - ui: { - SearchBar: () => {}, - }, - }, - setup: {}, -})); - -jest.mock('../../../../core_plugins/data/public', () => { - return { +const dataShim = { + ui: { SearchBar: () =>
, - SearchBarProps: {}, - }; -}); + }, +}; describe('TopNavMenu', () => { const TOP_NAV_ITEM_SELECTOR = 'TopNavMenuItem'; @@ -84,7 +74,12 @@ describe('TopNavMenu', () => { it('Should render search bar', () => { const component = shallowWithIntl( - + ); expect(component.find(TOP_NAV_ITEM_SELECTOR).length).toBe(0); diff --git a/src/legacy/core_plugins/kibana_react/public/top_nav_menu/top_nav_menu.tsx b/src/legacy/core_plugins/navigation/public/top_nav_menu/top_nav_menu.tsx similarity index 89% rename from src/legacy/core_plugins/kibana_react/public/top_nav_menu/top_nav_menu.tsx rename to src/legacy/core_plugins/navigation/public/top_nav_menu/top_nav_menu.tsx index aec91c2aa6bc6..14599e76470c0 100644 --- a/src/legacy/core_plugins/kibana_react/public/top_nav_menu/top_nav_menu.tsx +++ b/src/legacy/core_plugins/navigation/public/top_nav_menu/top_nav_menu.tsx @@ -24,13 +24,13 @@ import { I18nProvider } from '@kbn/i18n/react'; import { TopNavMenuData } from './top_nav_menu_data'; import { TopNavMenuItem } from './top_nav_menu_item'; -import { SearchBarProps } from '../../../../core_plugins/data/public'; -import { start as data } from '../../../data/public/legacy'; +import { SearchBarProps, DataStart } from '../../../../core_plugins/data/public'; -type Props = Partial & { +export type TopNavMenuProps = Partial & { appName: string; config?: TopNavMenuData[]; showSearchBar?: boolean; + data?: DataStart; }; /* @@ -42,8 +42,7 @@ type Props = Partial & { * **/ -export function TopNavMenu(props: Props) { - const { SearchBar } = data.ui; +export function TopNavMenu(props: TopNavMenuProps) { const { config, showSearchBar, ...searchBarProps } = props; function renderItems() { if (!config) return; @@ -58,7 +57,8 @@ export function TopNavMenu(props: Props) { function renderSearchBar() { // Validate presense of all required fields - if (!showSearchBar) return; + if (!showSearchBar || !props.data) return; + const { SearchBar } = props.data.ui; return ; } diff --git a/src/legacy/core_plugins/kibana_react/public/top_nav_menu/top_nav_menu_data.tsx b/src/legacy/core_plugins/navigation/public/top_nav_menu/top_nav_menu_data.tsx similarity index 100% rename from src/legacy/core_plugins/kibana_react/public/top_nav_menu/top_nav_menu_data.tsx rename to src/legacy/core_plugins/navigation/public/top_nav_menu/top_nav_menu_data.tsx diff --git a/src/legacy/core_plugins/navigation/public/top_nav_menu/top_nav_menu_extensions_registry.ts b/src/legacy/core_plugins/navigation/public/top_nav_menu/top_nav_menu_extensions_registry.ts new file mode 100644 index 0000000000000..c3eab3cce18e6 --- /dev/null +++ b/src/legacy/core_plugins/navigation/public/top_nav_menu/top_nav_menu_extensions_registry.ts @@ -0,0 +1,46 @@ +/* + * 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 { TopNavMenuData } from './top_nav_menu_data'; + +export class TopNavMenuExtensionsRegistry { + private menuItems: TopNavMenuData[]; + + constructor() { + this.menuItems = []; + } + + /** @public **/ + // Items registered into this registry will be appended to any TopNavMenu rendered in any application. + public register(menuItem: TopNavMenuData) { + this.menuItems.push(menuItem); + } + + /** @internal **/ + public getAll() { + return this.menuItems; + } + + /** @internal **/ + public clear() { + this.menuItems.length = 0; + } +} + +export type TopNavMenuExtensionsRegistrySetup = Pick; diff --git a/src/legacy/core_plugins/kibana_react/public/top_nav_menu/top_nav_menu_item.test.tsx b/src/legacy/core_plugins/navigation/public/top_nav_menu/top_nav_menu_item.test.tsx similarity index 100% rename from src/legacy/core_plugins/kibana_react/public/top_nav_menu/top_nav_menu_item.test.tsx rename to src/legacy/core_plugins/navigation/public/top_nav_menu/top_nav_menu_item.test.tsx diff --git a/src/legacy/core_plugins/kibana_react/public/top_nav_menu/top_nav_menu_item.tsx b/src/legacy/core_plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx similarity index 100% rename from src/legacy/core_plugins/kibana_react/public/top_nav_menu/top_nav_menu_item.tsx rename to src/legacy/core_plugins/navigation/public/top_nav_menu/top_nav_menu_item.tsx diff --git a/src/legacy/core_plugins/state_session_storage_redirect/public/index.js b/src/legacy/core_plugins/state_session_storage_redirect/public/index.js index eeed13edbe9f5..f64237000ae41 100644 --- a/src/legacy/core_plugins/state_session_storage_redirect/public/index.js +++ b/src/legacy/core_plugins/state_session_storage_redirect/public/index.js @@ -17,7 +17,6 @@ * under the License. */ -import 'ui/autoload/styles'; import chrome from 'ui/chrome'; import { hashUrl } from 'ui/state_management/state_hashing'; import uiRoutes from 'ui/routes'; 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 b545a261c8739..4467c31fca22c 100644 --- a/src/legacy/core_plugins/status_page/public/status_page.js +++ b/src/legacy/core_plugins/status_page/public/status_page.js @@ -17,7 +17,6 @@ * under the License. */ -import 'ui/autoload/styles'; import 'ui/i18n'; import chrome from 'ui/chrome'; import { npStart } from 'ui/new_platform'; diff --git a/src/legacy/server/http/integration_tests/default_route_provider.test.ts b/src/legacy/server/http/integration_tests/default_route_provider.test.ts index d74be851a3535..4898cb2b67852 100644 --- a/src/legacy/server/http/integration_tests/default_route_provider.test.ts +++ b/src/legacy/server/http/integration_tests/default_route_provider.test.ts @@ -44,7 +44,7 @@ describe('default route provider', () => { } throw Error(`unsupported ui setting: ${key}`); }, - getDefaults: () => { + getRegistered: () => { return { defaultRoute: { value: '/app/kibana', diff --git a/src/legacy/server/http/setup_default_route_provider.ts b/src/legacy/server/http/setup_default_route_provider.ts index f627cf30a3cff..0e7bcf1f56f6f 100644 --- a/src/legacy/server/http/setup_default_route_provider.ts +++ b/src/legacy/server/http/setup_default_route_provider.ts @@ -40,7 +40,7 @@ export function setupDefaultRouteProvider(server: Legacy.Server) { `Ignoring configured default route of '${defaultRoute}', as it is malformed.` ); - const fallbackRoute = uiSettings.getDefaults().defaultRoute.value; + const fallbackRoute = uiSettings.getRegistered().defaultRoute.value; const qualifiedFallbackRoute = `${request.getBasePath()}${fallbackRoute}`; return qualifiedFallbackRoute; diff --git a/src/legacy/server/saved_objects/routes/bulk_create.test.ts b/src/legacy/server/saved_objects/routes/bulk_create.test.ts index 1e041bb28f75f..b49554995aab6 100644 --- a/src/legacy/server/saved_objects/routes/bulk_create.test.ts +++ b/src/legacy/server/saved_objects/routes/bulk_create.test.ts @@ -22,11 +22,11 @@ import { createMockServer } from './_mock_server'; import { createBulkCreateRoute } from './bulk_create'; // Disable lint errors for imports from src/core/* until SavedObjects migration is complete // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { SavedObjectsClientMock } from '../../../../core/server/saved_objects/service/saved_objects_client.mock'; +import { savedObjectsClientMock } from '../../../../core/server/saved_objects/service/saved_objects_client.mock'; describe('POST /api/saved_objects/_bulk_create', () => { let server: Hapi.Server; - const savedObjectsClient = SavedObjectsClientMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); beforeEach(() => { savedObjectsClient.bulkCreate.mockImplementation(() => Promise.resolve('' as any)); diff --git a/src/legacy/server/saved_objects/routes/bulk_get.test.ts b/src/legacy/server/saved_objects/routes/bulk_get.test.ts index 546164be65c9f..e154649e2cf04 100644 --- a/src/legacy/server/saved_objects/routes/bulk_get.test.ts +++ b/src/legacy/server/saved_objects/routes/bulk_get.test.ts @@ -20,11 +20,11 @@ import Hapi from 'hapi'; import { createMockServer } from './_mock_server'; import { createBulkGetRoute } from './bulk_get'; -import { SavedObjectsClientMock } from '../../../../core/server/mocks'; +import { savedObjectsClientMock } from '../../../../core/server/mocks'; describe('POST /api/saved_objects/_bulk_get', () => { let server: Hapi.Server; - const savedObjectsClient = SavedObjectsClientMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); beforeEach(() => { savedObjectsClient.bulkGet.mockImplementation(() => diff --git a/src/legacy/server/saved_objects/routes/bulk_update.test.ts b/src/legacy/server/saved_objects/routes/bulk_update.test.ts index ee74ddfc535d2..dc21ab08035ce 100644 --- a/src/legacy/server/saved_objects/routes/bulk_update.test.ts +++ b/src/legacy/server/saved_objects/routes/bulk_update.test.ts @@ -20,11 +20,11 @@ import Hapi from 'hapi'; import { createMockServer } from './_mock_server'; import { createBulkUpdateRoute } from './bulk_update'; -import { SavedObjectsClientMock } from '../../../../core/server/mocks'; +import { savedObjectsClientMock } from '../../../../core/server/mocks'; describe('PUT /api/saved_objects/_bulk_update', () => { let server: Hapi.Server; - const savedObjectsClient = SavedObjectsClientMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); beforeEach(() => { server = createMockServer(); diff --git a/src/legacy/server/saved_objects/routes/create.test.ts b/src/legacy/server/saved_objects/routes/create.test.ts index 85096228c3175..4f096a9ee5c93 100644 --- a/src/legacy/server/saved_objects/routes/create.test.ts +++ b/src/legacy/server/saved_objects/routes/create.test.ts @@ -20,7 +20,7 @@ import Hapi from 'hapi'; import { createMockServer } from './_mock_server'; import { createCreateRoute } from './create'; -import { SavedObjectsClientMock } from '../../../../core/server/mocks'; +import { savedObjectsClientMock } from '../../../../core/server/mocks'; describe('POST /api/saved_objects/{type}', () => { let server: Hapi.Server; @@ -32,7 +32,7 @@ describe('POST /api/saved_objects/{type}', () => { references: [], attributes: {}, }; - const savedObjectsClient = SavedObjectsClientMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); beforeEach(() => { savedObjectsClient.create.mockImplementation(() => Promise.resolve(clientResponse)); diff --git a/src/legacy/server/saved_objects/routes/delete.test.ts b/src/legacy/server/saved_objects/routes/delete.test.ts index 9e2adcf9d3b91..f3e5e83771471 100644 --- a/src/legacy/server/saved_objects/routes/delete.test.ts +++ b/src/legacy/server/saved_objects/routes/delete.test.ts @@ -20,11 +20,11 @@ import Hapi from 'hapi'; import { createMockServer } from './_mock_server'; import { createDeleteRoute } from './delete'; -import { SavedObjectsClientMock } from '../../../../core/server/mocks'; +import { savedObjectsClientMock } from '../../../../core/server/mocks'; describe('DELETE /api/saved_objects/{type}/{id}', () => { let server: Hapi.Server; - const savedObjectsClient = SavedObjectsClientMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); beforeEach(() => { savedObjectsClient.delete.mockImplementation(() => Promise.resolve('{}')); diff --git a/src/legacy/server/saved_objects/routes/export.test.ts b/src/legacy/server/saved_objects/routes/export.test.ts index 2670535ab995e..93ca3a419e6df 100644 --- a/src/legacy/server/saved_objects/routes/export.test.ts +++ b/src/legacy/server/saved_objects/routes/export.test.ts @@ -28,14 +28,14 @@ import * as exportMock from '../../../../core/server/saved_objects/export'; import { createMockServer } from './_mock_server'; import { createExportRoute } from './export'; import { createListStream } from '../../../utils/streams'; -import { SavedObjectsClientMock } from '../../../../core/server/mocks'; +import { savedObjectsClientMock } from '../../../../core/server/mocks'; const getSortedObjectsForExport = exportMock.getSortedObjectsForExport as jest.Mock; describe('POST /api/saved_objects/_export', () => { let server: Hapi.Server; const savedObjectsClient = { - ...SavedObjectsClientMock.create(), + ...savedObjectsClientMock.create(), errors: {} as any, }; diff --git a/src/legacy/server/saved_objects/routes/find.test.ts b/src/legacy/server/saved_objects/routes/find.test.ts index 89cd0dd28d035..4bf5f57fec199 100644 --- a/src/legacy/server/saved_objects/routes/find.test.ts +++ b/src/legacy/server/saved_objects/routes/find.test.ts @@ -20,11 +20,11 @@ import Hapi from 'hapi'; import { createMockServer } from './_mock_server'; import { createFindRoute } from './find'; -import { SavedObjectsClientMock } from '../../../../core/server/mocks'; +import { savedObjectsClientMock } from '../../../../core/server/mocks'; describe('GET /api/saved_objects/_find', () => { let server: Hapi.Server; - const savedObjectsClient = SavedObjectsClientMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); const clientResponse = { total: 0, diff --git a/src/legacy/server/saved_objects/routes/get.test.ts b/src/legacy/server/saved_objects/routes/get.test.ts index 2f7eaea1bc770..7ede2a8b4d7b4 100644 --- a/src/legacy/server/saved_objects/routes/get.test.ts +++ b/src/legacy/server/saved_objects/routes/get.test.ts @@ -20,11 +20,11 @@ import Hapi from 'hapi'; import { createMockServer } from './_mock_server'; import { createGetRoute } from './get'; -import { SavedObjectsClientMock } from '../../../../core/server/mocks'; +import { savedObjectsClientMock } from '../../../../core/server/mocks'; describe('GET /api/saved_objects/{type}/{id}', () => { let server: Hapi.Server; - const savedObjectsClient = SavedObjectsClientMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); beforeEach(() => { savedObjectsClient.get.mockImplementation(() => diff --git a/src/legacy/server/saved_objects/routes/import.test.ts b/src/legacy/server/saved_objects/routes/import.test.ts index 1a0684a35ec79..2b8d9d7523507 100644 --- a/src/legacy/server/saved_objects/routes/import.test.ts +++ b/src/legacy/server/saved_objects/routes/import.test.ts @@ -20,11 +20,11 @@ import Hapi from 'hapi'; import { createMockServer } from './_mock_server'; import { createImportRoute } from './import'; -import { SavedObjectsClientMock } from '../../../../core/server/mocks'; +import { savedObjectsClientMock } from '../../../../core/server/mocks'; describe('POST /api/saved_objects/_import', () => { let server: Hapi.Server; - const savedObjectsClient = SavedObjectsClientMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); const emptyResponse = { saved_objects: [], total: 0, diff --git a/src/legacy/server/saved_objects/routes/resolve_import_errors.test.ts b/src/legacy/server/saved_objects/routes/resolve_import_errors.test.ts index 7988165207e63..44fa46bccfce5 100644 --- a/src/legacy/server/saved_objects/routes/resolve_import_errors.test.ts +++ b/src/legacy/server/saved_objects/routes/resolve_import_errors.test.ts @@ -20,11 +20,11 @@ import Hapi from 'hapi'; import { createMockServer } from './_mock_server'; import { createResolveImportErrorsRoute } from './resolve_import_errors'; -import { SavedObjectsClientMock } from '../../../../core/server/mocks'; +import { savedObjectsClientMock } from '../../../../core/server/mocks'; describe('POST /api/saved_objects/_resolve_import_errors', () => { let server: Hapi.Server; - const savedObjectsClient = SavedObjectsClientMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); beforeEach(() => { server = createMockServer(); diff --git a/src/legacy/server/saved_objects/routes/update.test.ts b/src/legacy/server/saved_objects/routes/update.test.ts index 69a6fe3030009..aaeaff489d30a 100644 --- a/src/legacy/server/saved_objects/routes/update.test.ts +++ b/src/legacy/server/saved_objects/routes/update.test.ts @@ -20,11 +20,11 @@ import Hapi from 'hapi'; import { createMockServer } from './_mock_server'; import { createUpdateRoute } from './update'; -import { SavedObjectsClientMock } from '../../../../core/server/mocks'; +import { savedObjectsClientMock } from '../../../../core/server/mocks'; describe('PUT /api/saved_objects/{type}/{id?}', () => { let server: Hapi.Server; - const savedObjectsClient = SavedObjectsClientMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); beforeEach(() => { const clientResponse = { diff --git a/src/legacy/ui/__tests__/ui_exports_replace_injected_vars.js b/src/legacy/ui/__tests__/ui_exports_replace_injected_vars.js index 20ff1d5bcfe0a..a58ec992495fd 100644 --- a/src/legacy/ui/__tests__/ui_exports_replace_injected_vars.js +++ b/src/legacy/ui/__tests__/ui_exports_replace_injected_vars.js @@ -69,7 +69,7 @@ describe('UiExports', function () { sandbox .stub(getUiSettingsServiceForRequestNS, 'getUiSettingsServiceForRequest') .returns({ - getDefaults: noop, + getRegistered: noop, getUserProvided: noop }); }); diff --git a/src/legacy/ui/field_formats/mixin/field_formats_mixin.ts b/src/legacy/ui/field_formats/mixin/field_formats_mixin.ts index 27b3967d6f46e..66466b96abe83 100644 --- a/src/legacy/ui/field_formats/mixin/field_formats_mixin.ts +++ b/src/legacy/ui/field_formats/mixin/field_formats_mixin.ts @@ -28,9 +28,9 @@ export function fieldFormatsMixin(kbnServer: any, server: Legacy.Server) { // for use outside of the request context, for special cases server.decorate('server', 'fieldFormatServiceFactory', async function(uiSettings) { const uiConfigs = await uiSettings.getAll(); - const uiSettingDefaults = uiSettings.getDefaults(); - Object.keys(uiSettingDefaults).forEach(key => { - if (has(uiConfigs, key) && uiSettingDefaults[key].type === 'json') { + const registeredUiSettings = uiSettings.getRegistered(); + Object.keys(registeredUiSettings).forEach(key => { + if (has(uiConfigs, key) && registeredUiSettings[key].type === 'json') { uiConfigs[key] = JSON.parse(uiConfigs[key]); } }); diff --git a/src/legacy/ui/public/agg_types/__tests__/metrics/median.js b/src/legacy/ui/public/agg_types/__tests__/metrics/median.js deleted file mode 100644 index df65afa5e4222..0000000000000 --- a/src/legacy/ui/public/agg_types/__tests__/metrics/median.js +++ /dev/null @@ -1,73 +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 expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { VisProvider } from '../../../vis'; -import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; - -describe('AggTypeMetricMedianProvider class', function () { - let indexPattern; - let aggDsl; - - beforeEach(ngMock.module('kibana')); - beforeEach(ngMock.inject(function (Private) { - const Vis = Private(VisProvider); - indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - - const vis = new Vis(indexPattern, { - 'title': 'New Visualization', - 'type': 'metric', - 'params': { - 'fontSize': 60 - }, - 'aggs': [ - { - 'id': '1', - 'type': 'median', - 'schema': 'metric', - 'params': { - 'field': 'bytes', - 'percents': [ - 50 - ] - } - } - ], - 'listeners': {} - }); - - // Grab the aggConfig off the vis (we don't actually use the vis for - // anything else) - const aggConfig = vis.aggs.aggs[0]; - aggDsl = aggConfig.toDsl(); - })); - - it('requests the percentiles aggregation in the Elasticsearch query DSL', function () { - expect(Object.keys(aggDsl)[0]).to.be('percentiles'); - }); - - it ('asks Elasticsearch for the 50th percentile', function () { - expect(aggDsl.percentiles.percents).to.eql([50]); - }); - - it ('asks Elasticsearch for array-based values in the aggregation response', function () { - expect(aggDsl.percentiles.keyed).to.be(false); - }); -}); diff --git a/src/legacy/ui/public/agg_types/__tests__/metrics/parent_pipeline.js b/src/legacy/ui/public/agg_types/__tests__/metrics/parent_pipeline.js deleted file mode 100644 index e4ca6075c624b..0000000000000 --- a/src/legacy/ui/public/agg_types/__tests__/metrics/parent_pipeline.js +++ /dev/null @@ -1,220 +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 expect from '@kbn/expect'; -import sinon from 'sinon'; -import ngMock from 'ng_mock'; -import { derivativeMetricAgg } from '../../metrics/derivative'; -import { cumulativeSumMetricAgg } from '../../metrics/cumulative_sum'; -import { movingAvgMetricAgg } from '../../metrics/moving_avg'; -import { serialDiffMetricAgg } from '../../metrics/serial_diff'; -import { VisProvider } from '../../../vis'; -import StubbedIndexPattern from 'fixtures/stubbed_logstash_index_pattern'; - -const metrics = [ - { name: 'derivative', title: 'Derivative', agg: derivativeMetricAgg }, - { name: 'cumulative_sum', title: 'Cumulative Sum', agg: cumulativeSumMetricAgg }, - { name: 'moving_avg', title: 'Moving Avg', agg: movingAvgMetricAgg, dslName: 'moving_fn' }, - { name: 'serial_diff', title: 'Serial Diff', agg: serialDiffMetricAgg }, -]; - -describe('parent pipeline aggs', function () { - metrics.forEach(metric => { - describe(`${metric.title} metric`, function () { - - let aggDsl; - let metricAgg; - let aggConfig; - - function init(settings) { - ngMock.module('kibana'); - ngMock.inject(function (Private) { - const Vis = Private(VisProvider); - const indexPattern = Private(StubbedIndexPattern); - indexPattern.stubSetFieldFormat('bytes', 'bytes'); - metricAgg = metric.agg; - - const params = settings || { - metricAgg: '1', - customMetric: null - }; - - const vis = new Vis(indexPattern, { - title: 'New Visualization', - type: 'metric', - params: { - fontSize: 60 - }, - aggs: [ - { - id: '1', - type: 'count', - schema: 'metric' - }, - { - id: '2', - type: metric.name, - schema: 'metric', - params - }, - { - id: '3', - type: 'max', - params: { field: '@timestamp' }, - schema: 'metric' - } - ], - listeners: {} - }); - - // Grab the aggConfig off the vis (we don't actually use the vis for anything else) - aggConfig = vis.aggs.aggs[1]; - aggDsl = aggConfig.toDsl(vis.aggs); - }); - } - - it(`should return a label prefixed with ${metric.title} of`, function () { - init(); - expect(metricAgg.makeLabel(aggConfig)).to.eql(`${metric.title} of Count`); - }); - - it(`should return a label ${metric.title} of max bytes`, function () { - init({ - metricAgg: 'custom', - customMetric: { - id: '1-orderAgg', - type: 'max', - params: { field: 'bytes' }, - schema: 'orderAgg' - } - }); - expect(metricAgg.makeLabel(aggConfig)).to.eql(`${metric.title} of Max bytes`); - }); - - it(`should return a label prefixed with number of ${metric.title.toLowerCase()}`, function () { - init({ - metricAgg: 'custom', - customMetric: { - id: '2-orderAgg', - type: metric.name, - params: { - buckets_path: 'custom', - customMetric: { - id: '2-orderAgg-orderAgg', - type: 'count', - schema: 'orderAgg' - } - }, - schema: 'orderAgg' - } - }); - expect(metricAgg.makeLabel(aggConfig)).to.eql(`2. ${metric.title.toLowerCase()} of Count`); - }); - - it('should set parent aggs', function () { - init({ - metricAgg: 'custom', - customMetric: { - id: '2-metric', - type: 'max', - params: { field: 'bytes' }, - schema: 'orderAgg' - } - }); - expect(aggDsl[metric.dslName || metric.name].buckets_path).to.be('2-metric'); - expect(aggDsl.parentAggs['2-metric'].max.field).to.be('bytes'); - }); - - it('should set nested parent aggs', function () { - init({ - metricAgg: 'custom', - customMetric: { - id: '2-metric', - type: metric.name, - params: { - buckets_path: 'custom', - customMetric: { - id: '2-metric-metric', - type: 'max', - params: { field: 'bytes' }, - schema: 'orderAgg' - } - }, - schema: 'orderAgg' - } - }); - expect(aggDsl[metric.dslName || metric.name].buckets_path).to.be('2-metric'); - expect(aggDsl.parentAggs['2-metric'][metric.dslName || metric.name].buckets_path).to.be('2-metric-metric'); - }); - - it('should have correct formatter', function () { - init({ - metricAgg: '3' - }); - expect(metricAgg.getFormat(aggConfig).type.id).to.be('date'); - }); - - it('should have correct customMetric nested formatter', function () { - init({ - metricAgg: 'custom', - customMetric: { - id: '2-metric', - type: metric.name, - params: { - buckets_path: 'custom', - customMetric: { - id: '2-metric-metric', - type: 'max', - params: { field: 'bytes' }, - schema: 'orderAgg' - } - }, - schema: 'orderAgg' - } - }); - expect(metricAgg.getFormat(aggConfig).type.id).to.be('bytes'); - }); - - it('should call modifyAggConfigOnSearchRequestStart for its customMetric\'s parameters', () => { - init({ - metricAgg: 'custom', - customMetric: { - id: '2-metric', - type: 'max', - params: { field: 'bytes' }, - schema: 'orderAgg' - } - }); - - const searchSource = {}; - const customMetricSpy = sinon.spy(); - const customMetric = aggConfig.params.customMetric; - - // Attach a modifyAggConfigOnSearchRequestStart with a spy to the first parameter - customMetric.type.params[0].modifyAggConfigOnSearchRequestStart = customMetricSpy; - - aggConfig.type.params.forEach(param => { - param.modifyAggConfigOnSearchRequestStart(aggConfig, searchSource); - }); - expect(customMetricSpy.calledWith(customMetric, searchSource)).to.be(true); - }); - }); - }); - -}); diff --git a/src/legacy/ui/public/agg_types/__tests__/metrics/percentile_ranks.js b/src/legacy/ui/public/agg_types/__tests__/metrics/percentile_ranks.js deleted file mode 100644 index 1f767f8886262..0000000000000 --- a/src/legacy/ui/public/agg_types/__tests__/metrics/percentile_ranks.js +++ /dev/null @@ -1,57 +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 expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { percentileRanksMetricAgg } from '../../metrics/percentile_ranks'; -import { VisProvider } from '../../../vis'; -import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; - -describe('AggTypesMetricsPercentileRanksProvider class', function () { - - let Vis; - let indexPattern; - - beforeEach(ngMock.module('kibana')); - beforeEach(ngMock.inject(function (Private) { - Vis = Private(VisProvider); - indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - })); - - it('uses the custom label if it is set', function () { - const vis = new Vis(indexPattern, {}); - - // Grab the aggConfig off the vis (we don't actually use the vis for - // anything else) - const aggConfig = vis.aggs.aggs[0]; - aggConfig.params.customLabel = 'my custom field label'; - aggConfig.params.values = [ 5000, 10000 ]; - aggConfig.params.field = { - displayName: 'bytes' - }; - - const responseAggs = percentileRanksMetricAgg.getResponseAggs(aggConfig); - const percentileRankLabelFor5kBytes = responseAggs[0].makeLabel(); - const percentileRankLabelFor10kBytes = responseAggs[1].makeLabel(); - - expect(percentileRankLabelFor5kBytes).to.be('Percentile rank 5,000 of "my custom field label"'); - expect(percentileRankLabelFor10kBytes).to.be('Percentile rank 10,000 of "my custom field label"'); - }); - -}); diff --git a/src/legacy/ui/public/agg_types/__tests__/metrics/percentiles.js b/src/legacy/ui/public/agg_types/__tests__/metrics/percentiles.js deleted file mode 100644 index afbf8b95d6e73..0000000000000 --- a/src/legacy/ui/public/agg_types/__tests__/metrics/percentiles.js +++ /dev/null @@ -1,55 +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 expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { percentilesMetricAgg } from '../../metrics/percentiles'; -import { VisProvider } from '../../../vis'; -import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; - -describe('AggTypesMetricsPercentilesProvider class', function () { - - let Vis; - let indexPattern; - - beforeEach(ngMock.module('kibana')); - beforeEach(ngMock.inject(function (Private) { - Vis = Private(VisProvider); - indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - })); - - it('uses the custom label if it is set', function () { - const vis = new Vis(indexPattern, {}); - - // Grab the aggConfig off the vis (we don't actually use the vis for - // anything else) - const aggConfig = vis.aggs.aggs[0]; - aggConfig.params.customLabel = 'prince'; - aggConfig.params.percents = [ 95 ]; - aggConfig.params.field = { - displayName: 'bytes' - }; - - const responseAggs = percentilesMetricAgg.getResponseAggs(aggConfig); - const ninetyFifthPercentileLabel = responseAggs[0].makeLabel(); - - expect(ninetyFifthPercentileLabel).to.be('95th percentile of prince'); - }); - -}); diff --git a/src/legacy/ui/public/agg_types/__tests__/metrics/sibling_pipeline.js b/src/legacy/ui/public/agg_types/__tests__/metrics/sibling_pipeline.js deleted file mode 100644 index aba5db9cedadf..0000000000000 --- a/src/legacy/ui/public/agg_types/__tests__/metrics/sibling_pipeline.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 expect from '@kbn/expect'; -import sinon from 'sinon'; -import ngMock from 'ng_mock'; -import { bucketSumMetricAgg } from '../../metrics/bucket_sum'; -import { bucketAvgMetricAgg } from '../../metrics/bucket_avg'; -import { bucketMinMetricAgg } from '../../metrics/bucket_min'; -import { bucketMaxMetricAgg } from '../../metrics/bucket_max'; -import { VisProvider } from '../../../vis'; -import StubbedIndexPattern from 'fixtures/stubbed_logstash_index_pattern'; - -const metrics = [ - { name: 'sum_bucket', title: 'Overall Sum', provider: bucketSumMetricAgg }, - { name: 'avg_bucket', title: 'Overall Average', provider: bucketAvgMetricAgg }, - { name: 'min_bucket', title: 'Overall Min', provider: bucketMinMetricAgg }, - { name: 'max_bucket', title: 'Overall Max', provider: bucketMaxMetricAgg }, -]; - -describe('sibling pipeline aggs', function () { - metrics.forEach(metric => { - describe(`${metric.title} metric`, function () { - - let aggDsl; - let metricAgg; - let aggConfig; - - function init(settings) { - ngMock.module('kibana'); - ngMock.inject(function (Private) { - const Vis = Private(VisProvider); - const indexPattern = Private(StubbedIndexPattern); - indexPattern.stubSetFieldFormat('bytes', 'bytes'); - metricAgg = metric.provider; - - const params = settings || { - customMetric: { - id: '5', - type: 'count', - schema: 'metric' - }, - customBucket: { - id: '6', - type: 'date_histogram', - schema: 'bucket', - params: { field: '@timestamp', interval: '10s' } - } - }; - - const vis = new Vis(indexPattern, { - title: 'New Visualization', - type: 'metric', - params: { - fontSize: 60 - }, - aggs: [ - { - id: '1', - type: 'count', - schema: 'metric' - }, - { - id: '2', - type: metric.name, - schema: 'metric', - params - } - ], - listeners: {} - }); - - // Grab the aggConfig off the vis (we don't actually use the vis for anything else) - aggConfig = vis.aggs.aggs[1]; - aggDsl = aggConfig.toDsl(vis.aggs); - }); - } - - it(`should return a label prefixed with ${metric.title} of`, function () { - init(); - expect(metricAgg.makeLabel(aggConfig)).to.eql(`${metric.title} of Count`); - }); - - it('should set parent aggs', function () { - init(); - expect(aggDsl[metric.name].buckets_path).to.be('2-bucket>_count'); - expect(aggDsl.parentAggs['2-bucket'].date_histogram).to.not.be.undefined; - }); - - it('should set nested parent aggs', function () { - init({ - customMetric: { - id: '5', - type: 'avg', - schema: 'metric', - params: { field: 'bytes' }, - }, - customBucket: { - id: '6', - type: 'date_histogram', - schema: 'bucket', - params: { field: '@timestamp', interval: '10s', }, - } - }); - expect(aggDsl[metric.name].buckets_path).to.be('2-bucket>2-metric'); - expect(aggDsl.parentAggs['2-bucket'].date_histogram).to.not.be.undefined; - expect(aggDsl.parentAggs['2-bucket'].aggs['2-metric'].avg.field).to.equal('bytes'); - }); - - it('should have correct formatter', function () { - init({ - customMetric: { - id: '5', - type: 'avg', - schema: 'metric', - params: { field: 'bytes' }, - }, - customBucket: { - id: '6', - type: 'date_histogram', - schema: 'bucket', - params: { field: '@timestamp', interval: '10s' }, - } - }); - expect(metricAgg.getFormat(aggConfig).type.id).to.be('bytes'); - }); - - it('should call modifyAggConfigOnSearchRequestStart for nested aggs\' parameters', () => { - init(); - - const searchSource = {}; - const customMetricSpy = sinon.spy(); - const customBucketSpy = sinon.spy(); - const { customMetric, customBucket } = aggConfig.params; - - // Attach a modifyAggConfigOnSearchRequestStart with a spy to the first parameter - customMetric.type.params[0].modifyAggConfigOnSearchRequestStart = customMetricSpy; - customBucket.type.params[0].modifyAggConfigOnSearchRequestStart = customBucketSpy; - - aggConfig.type.params.forEach(param => { - param.modifyAggConfigOnSearchRequestStart(aggConfig, searchSource); - }); - expect(customMetricSpy.calledWith(customMetric, searchSource)).to.be(true); - expect(customBucketSpy.calledWith(customBucket, searchSource)).to.be(true); - }); - - }); - }); - -}); diff --git a/src/legacy/ui/public/agg_types/__tests__/metrics/std_deviation.js b/src/legacy/ui/public/agg_types/__tests__/metrics/std_deviation.js deleted file mode 100644 index b31794f6d9ffe..0000000000000 --- a/src/legacy/ui/public/agg_types/__tests__/metrics/std_deviation.js +++ /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 expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { stdDeviationMetricAgg } from '../../metrics/std_deviation'; -import { VisProvider } from '../../../vis'; -import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; - -describe('AggTypeMetricStandardDeviationProvider class', function () { - - let Vis; - let indexPattern; - - beforeEach(ngMock.module('kibana')); - beforeEach(ngMock.inject(function (Private) { - Vis = Private(VisProvider); - indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - })); - - it('uses the custom label if it is set', function () { - const vis = new Vis(indexPattern, {}); - - // Grab the aggConfig off the vis (we don't actually use the vis for - // anything else) - const aggConfig = vis.aggs.aggs[0]; - aggConfig.params.customLabel = 'custom label'; - aggConfig.params.field = { - displayName: 'memory' - }; - - const responseAggs = stdDeviationMetricAgg.getResponseAggs(aggConfig); - const lowerStdDevLabel = responseAggs[0].makeLabel(); - const upperStdDevLabel = responseAggs[1].makeLabel(); - - expect(lowerStdDevLabel).to.be('Lower custom label'); - expect(upperStdDevLabel).to.be('Upper custom label'); - }); - - it('uses the default labels if custom label is not set', function () { - const vis = new Vis(indexPattern, {}); - - // Grab the aggConfig off the vis (we don't actually use the vis for - // anything else) - const aggConfig = vis.aggs.aggs[0]; - aggConfig.params.field = { - displayName: 'memory' - }; - - const responseAggs = stdDeviationMetricAgg.getResponseAggs(aggConfig); - const lowerStdDevLabel = responseAggs[0].makeLabel(); - const upperStdDevLabel = responseAggs[1].makeLabel(); - - expect(lowerStdDevLabel).to.be('Lower Standard Deviation of memory'); - expect(upperStdDevLabel).to.be('Upper Standard Deviation of memory'); - }); - -}); diff --git a/src/legacy/ui/public/agg_types/__tests__/metrics/top_hit.js b/src/legacy/ui/public/agg_types/__tests__/metrics/top_hit.js deleted file mode 100644 index 927d73793f469..0000000000000 --- a/src/legacy/ui/public/agg_types/__tests__/metrics/top_hit.js +++ /dev/null @@ -1,376 +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 _ from 'lodash'; -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { topHitMetricAgg } from '../../metrics/top_hit'; -import { VisProvider } from '../../../vis'; -import StubbedIndexPattern from 'fixtures/stubbed_logstash_index_pattern'; - -describe('Top hit metric', function () { - let aggDsl; - let aggConfig; - - function init({ field, sortOrder = 'desc', aggregate = 'concat', size = 1 }) { - ngMock.module('kibana'); - ngMock.inject(function (Private) { - const Vis = Private(VisProvider); - const indexPattern = Private(StubbedIndexPattern); - - const params = {}; - if (field) { - params.field = field; - } - params.sortOrder = { - value: sortOrder - }; - params.aggregate = { - value: aggregate - }; - params.size = size; - const vis = new Vis(indexPattern, { - title: 'New Visualization', - type: 'metric', - params: { - fontSize: 60 - }, - aggs: [ - { - id: '1', - type: 'top_hits', - schema: 'metric', - params - } - ], - listeners: {} - }); - - // Grab the aggConfig off the vis (we don't actually use the vis for anything else) - aggConfig = vis.aggs.aggs[0]; - aggDsl = aggConfig.toDsl(); - }); - } - - it('should return a label prefixed with Last if sorting in descending order', function () { - init({ field: 'bytes' }); - expect(topHitMetricAgg.makeLabel(aggConfig)).to.eql('Last bytes'); - }); - - it('should return a label prefixed with First if sorting in ascending order', function () { - init({ - field: 'bytes', - sortOrder: 'asc' - }); - expect(topHitMetricAgg.makeLabel(aggConfig)).to.eql('First bytes'); - }); - - it('should request the _source field', function () { - init({ field: '_source' }); - expect(aggDsl.top_hits._source).to.be(true); - expect(aggDsl.top_hits.docvalue_fields).to.be(undefined); - }); - - it('requests both source and docvalues_fields for non-text aggregatable fields', function () { - init({ field: 'bytes' }); - expect(aggDsl.top_hits._source).to.be('bytes'); - expect(aggDsl.top_hits.docvalue_fields).to.eql([ { field: 'bytes', format: 'use_field_mapping' } ]); - }); - - it('requests both source and docvalues_fields for date aggregatable fields', function () { - init({ field: '@timestamp' }); - expect(aggDsl.top_hits._source).to.be('@timestamp'); - expect(aggDsl.top_hits.docvalue_fields).to.eql([ { field: '@timestamp', format: 'date_time' } ]); - }); - - it('requests just source for aggregatable text fields', function () { - init({ field: 'machine.os' }); - expect(aggDsl.top_hits._source).to.be('machine.os'); - expect(aggDsl.top_hits.docvalue_fields).to.be(undefined); - }); - - it('requests just source for not-aggregatable text fields', function () { - init({ field: 'non-sortable' }); - expect(aggDsl.top_hits._source).to.be('non-sortable'); - expect(aggDsl.top_hits.docvalue_fields).to.be(undefined); - }); - - it('requests just source for not-aggregatable, non-text fields', function () { - init({ field: 'hashed' }); - expect(aggDsl.top_hits._source).to.be('hashed'); - expect(aggDsl.top_hits.docvalue_fields).to.be(undefined); - }); - - describe('try to get the value from the top hit', function () { - it('should return null if there is no hit', function () { - const bucket = { - '1': { - hits: { - hits: [] - } - } - }; - - init({ field: '@tags' }); - expect(topHitMetricAgg.getValue(aggConfig, bucket)).to.be(null); - }); - - it('should return undefined if the field does not appear in the source', function () { - const bucket = { - '1': { - hits: { - hits: [ - { - _source: { - bytes: 123 - } - } - ] - } - } - }; - - init({ field: '@tags' }); - expect(topHitMetricAgg.getValue(aggConfig, bucket)).to.be(undefined); - }); - - it('should return the field value from the top hit', function () { - const bucket = { - '1': { - hits: { - hits: [ - { - _source: { - '@tags': 'aaa' - } - } - ] - } - } - }; - - init({ field: '@tags' }); - expect(topHitMetricAgg.getValue(aggConfig, bucket)).to.be('aaa'); - }); - - it('should return the object if the field value is an object', function () { - const bucket = { - '1': { - hits: { - hits: [ - { - _source: { - '@tags': { - label: 'aaa' - } - } - } - ] - } - } - }; - - init({ field: '@tags' }); - expect(topHitMetricAgg.getValue(aggConfig, bucket)).to.eql({ label: 'aaa' }); - }); - - it('should return an array if the field has more than one values', function () { - const bucket = { - '1': { - hits: { - hits: [ - { - _source: { - '@tags': [ 'aaa', 'bbb' ] - } - } - ] - } - } - }; - - init({ field: '@tags' }); - expect(topHitMetricAgg.getValue(aggConfig, bucket)).to.eql([ 'aaa', 'bbb' ]); - }); - - it('should get the value from the doc_values field if the source does not have that field', function () { - const bucket = { - '1': { - hits: { - hits: [ - { - _source: { - 'machine.os': 'linux' - }, - fields: { - 'machine.os.raw': [ 'linux' ] - } - } - ] - } - } - }; - - init({ field: 'machine.os.raw' }); - expect(topHitMetricAgg.getValue(aggConfig, bucket)).to.be('linux'); - }); - - it('should return undefined if the field is not in the source nor in the doc_values field', function () { - const bucket = { - '1': { - hits: { - hits: [ - { - _source: { - bytes: 12345 - }, - fields: { - bytes: 12345 - } - } - ] - } - } - }; - - init({ field: 'machine.os.raw' }); - expect(topHitMetricAgg.getValue(aggConfig, bucket)).to.be(undefined); - }); - - describe('Multivalued field and first/last X docs', function () { - it('should return a label prefixed with Last X docs if sorting in descending order', function () { - init({ - field: 'bytes', - size: 2 - }); - expect(topHitMetricAgg.makeLabel(aggConfig)).to.eql('Last 2 bytes'); - }); - - it('should return a label prefixed with First X docs if sorting in ascending order', function () { - init({ - field: 'bytes', - size: 2, - sortOrder: 'asc' - }); - expect(topHitMetricAgg.makeLabel(aggConfig)).to.eql('First 2 bytes'); - }); - - [ - { - description: 'concat values with a comma', - type: 'concat', - data: [ 1, 2, 3 ], - result: [ 1, 2, 3 ] - }, - { - description: 'sum up the values', - type: 'sum', - data: [ 1, 2, 3 ], - result: 6 - }, - { - description: 'take the minimum value', - type: 'min', - data: [ 1, 2, 3 ], - result: 1 - }, - { - description: 'take the maximum value', - type: 'max', - data: [ 1, 2, 3 ], - result: 3 - }, - { - description: 'take the average value', - type: 'average', - data: [ 1, 2, 3 ], - result: 2 - }, - { - description: 'support null/undefined', - type: 'min', - data: [ undefined, null ], - result: null - }, - { - description: 'support null/undefined', - type: 'max', - data: [ undefined, null ], - result: null - }, - { - description: 'support null/undefined', - type: 'sum', - data: [ undefined, null ], - result: null - }, - { - description: 'support null/undefined', - type: 'average', - data: [ undefined, null ], - result: null - } - ] - .forEach(agg => { - it(`should return the result of the ${agg.type} aggregation over the last doc - ${agg.description}`, function () { - const bucket = { - '1': { - hits: { - hits: [ - { - _source: { - bytes: agg.data - } - } - ] - } - } - }; - - init({ field: 'bytes', aggregate: agg.type }); - expect(topHitMetricAgg.getValue(aggConfig, bucket)).to.eql(agg.result); - }); - - it(`should return the result of the ${agg.type} aggregation over the last X docs - ${agg.description}`, function () { - const bucket = { - '1': { - hits: { - hits: [ - { - _source: { - bytes: _.dropRight(agg.data, 1) - } - }, - { - _source: { - bytes: _.last(agg.data) - } - } - ] - } - } - }; - - init({ field: 'bytes', aggregate: agg.type }); - expect(topHitMetricAgg.getValue(aggConfig, bucket)).to.eql(agg.result); - }); - }); - }); - }); -}); diff --git a/src/legacy/ui/public/agg_types/metrics/get_response_agg_config_class.ts b/src/legacy/ui/public/agg_types/metrics/lib/get_response_agg_config_class.ts similarity index 75% rename from src/legacy/ui/public/agg_types/metrics/get_response_agg_config_class.ts rename to src/legacy/ui/public/agg_types/metrics/lib/get_response_agg_config_class.ts index 34658431a96ab..054543de3dd06 100644 --- a/src/legacy/ui/public/agg_types/metrics/get_response_agg_config_class.ts +++ b/src/legacy/ui/public/agg_types/metrics/lib/get_response_agg_config_class.ts @@ -16,9 +16,8 @@ * specific language governing permissions and limitations * under the License. */ - import { assign } from 'lodash'; -import { IMetricAggConfig } from './metric_agg_type'; +import { IMetricAggConfig } from '../metric_agg_type'; /** * Get the ResponseAggConfig class for an aggConfig, @@ -41,10 +40,7 @@ export interface IResponseAggConfig extends IMetricAggConfig { parentId: IMetricAggConfig['id']; } -export const create = ( - parentAgg: IMetricAggConfig, - props: Partial -): IMetricAggConfig => { +export const create = (parentAgg: IMetricAggConfig, props: Partial) => { /** * AggConfig "wrapper" for multi-value metric aggs which * need to modify AggConfig behavior for each value produced. @@ -52,23 +48,21 @@ export const create = ( * @param {string|number} key - the key or index that identifies * this part of the multi-value */ - class ResponseAggConfig { - id: IMetricAggConfig['id']; - key: string | number; - parentId: IMetricAggConfig['id']; - - constructor(key: string) { - this.key = key; - this.parentId = parentAgg.id; + function ResponseAggConfig(this: IResponseAggConfig, key: string) { + const parentId = parentAgg.id; + let id; - const subId = String(key); + const subId = String(key); - if (subId.indexOf('.') > -1) { - this.id = this.parentId + "['" + subId.replace(/'/g, "\\'") + "']"; - } else { - this.id = this.parentId + '.' + subId; - } + if (subId.indexOf('.') > -1) { + id = parentId + "['" + subId.replace(/'/g, "\\'") + "']"; + } else { + id = parentId + '.' + subId; } + + this.id = id; + this.key = key; + this.parentId = parentId; } ResponseAggConfig.prototype = Object.create(parentAgg); @@ -76,5 +70,5 @@ export const create = ( assign(ResponseAggConfig.prototype, props); - return (ResponseAggConfig as unknown) as IMetricAggConfig; + return ResponseAggConfig; }; diff --git a/src/legacy/ui/public/agg_types/__tests__/metrics/lib/make_nested_label.js b/src/legacy/ui/public/agg_types/metrics/lib/make_nested_label.test.ts similarity index 62% rename from src/legacy/ui/public/agg_types/__tests__/metrics/lib/make_nested_label.js rename to src/legacy/ui/public/agg_types/metrics/lib/make_nested_label.test.ts index f4ba550557874..aed5bd630d3d2 100644 --- a/src/legacy/ui/public/agg_types/__tests__/metrics/lib/make_nested_label.js +++ b/src/legacy/ui/public/agg_types/metrics/lib/make_nested_label.test.ts @@ -17,40 +17,43 @@ * under the License. */ -import expect from '@kbn/expect'; -import { makeNestedLabel } from '../../../metrics/lib/make_nested_label'; +import { makeNestedLabel } from './make_nested_label'; +import { IMetricAggConfig } from 'ui/agg_types/metrics/metric_agg_type'; -describe('metric agg make_nested_label', function () { - - function generateAggConfig(metricLabel) { - return { +describe('metric agg make_nested_label', () => { + const generateAggConfig = (metricLabel: string): IMetricAggConfig => { + return ({ params: { customMetric: { - makeLabel: () => { return metricLabel; } - } + makeLabel: () => { + return metricLabel; + }, + }, }, - getParam(key) { + getParam(this: IMetricAggConfig, key: string) { return this.params[key]; - } - }; - } + }, + } as unknown) as IMetricAggConfig; + }; - it('should return a metric label with prefix', function () { + it('should return a metric label with prefix', () => { const aggConfig = generateAggConfig('Count'); const label = makeNestedLabel(aggConfig, 'derivative'); - expect(label).to.eql('Derivative of Count'); + + expect(label).toEqual('Derivative of Count'); }); - it('should return a numbered prefix', function () { + it('should return a numbered prefix', () => { const aggConfig = generateAggConfig('Derivative of Count'); const label = makeNestedLabel(aggConfig, 'derivative'); - expect(label).to.eql('2. derivative of Count'); + + expect(label).toEqual('2. derivative of Count'); }); - it('should return a prefix with correct order', function () { + it('should return a prefix with correct order', () => { const aggConfig = generateAggConfig('3. derivative of Count'); const label = makeNestedLabel(aggConfig, 'derivative'); - expect(label).to.eql('4. derivative of Count'); - }); + expect(label).toEqual('4. derivative of Count'); + }); }); diff --git a/src/legacy/ui/public/agg_types/metrics/median.test.ts b/src/legacy/ui/public/agg_types/metrics/median.test.ts new file mode 100644 index 0000000000000..819c24f135cdc --- /dev/null +++ b/src/legacy/ui/public/agg_types/metrics/median.test.ts @@ -0,0 +1,69 @@ +/* + * 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 { AggConfigs } from '../agg_configs'; +import { METRIC_TYPES } from './metric_agg_types'; + +jest.mock('ui/new_platform'); + +describe('AggTypeMetricMedianProvider class', () => { + let aggConfigs: AggConfigs; + + beforeEach(() => { + const field = { + name: 'bytes', + }; + const indexPattern = { + id: '1234', + title: 'logstash-*', + fields: { + getByName: () => field, + filter: () => [field], + }, + } as any; + + aggConfigs = new AggConfigs( + indexPattern, + [ + { + id: METRIC_TYPES.MEDIAN, + type: METRIC_TYPES.MEDIAN, + schema: 'metric', + params: { + field: 'bytes', + percents: [70], + }, + }, + ], + null + ); + }); + + it('requests the percentiles aggregation in the Elasticsearch query DSL', () => { + const dsl: Record = aggConfigs.toDsl(); + + expect(dsl.median.percentiles.percents).toEqual([70]); + }); + + it('asks Elasticsearch for array-based values in the aggregation response', () => { + const dsl: Record = aggConfigs.toDsl(); + + expect(dsl.median.percentiles.keyed).toBeFalsy(); + }); +}); diff --git a/src/legacy/ui/public/agg_types/metrics/parent_pipeline.test.ts b/src/legacy/ui/public/agg_types/metrics/parent_pipeline.test.ts new file mode 100644 index 0000000000000..bf88adcee92b7 --- /dev/null +++ b/src/legacy/ui/public/agg_types/metrics/parent_pipeline.test.ts @@ -0,0 +1,248 @@ +/* + * 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 sinon from 'sinon'; +import { derivativeMetricAgg } from './derivative'; +import { cumulativeSumMetricAgg } from './cumulative_sum'; +import { movingAvgMetricAgg } from './moving_avg'; +import { serialDiffMetricAgg } from './serial_diff'; +import { AggConfigs } from 'ui/agg_types'; +import { IMetricAggConfig, MetricAggType } from 'ui/agg_types/metrics/metric_agg_type'; + +jest.mock('../../vis/editors/default/schemas', () => { + class MockedSchemas { + all = [{}]; + } + return { + Schemas: jest.fn().mockImplementation(() => new MockedSchemas()), + }; +}); + +jest.mock('../../vis/editors/default/controls/sub_metric', () => { + return { + SubMetricParamEditor() {}, + }; +}); + +jest.mock('../../vis/editors/default/controls/sub_agg', () => { + return { + SubAggParamEditor() {}, + }; +}); + +jest.mock('ui/new_platform'); + +describe('parent pipeline aggs', function() { + const metrics = [ + { name: 'derivative', title: 'Derivative', provider: derivativeMetricAgg }, + { name: 'cumulative_sum', title: 'Cumulative Sum', provider: cumulativeSumMetricAgg }, + { name: 'moving_avg', title: 'Moving Avg', provider: movingAvgMetricAgg, dslName: 'moving_fn' }, + { name: 'serial_diff', title: 'Serial Diff', provider: serialDiffMetricAgg }, + ]; + + metrics.forEach(metric => { + describe(`${metric.title} metric`, () => { + let aggDsl: Record; + let metricAgg: MetricAggType; + let aggConfig: IMetricAggConfig; + + const init = ( + params: any = { + metricAgg: '1', + customMetric: null, + } + ) => { + const field = { + name: 'field', + format: { + type: { + id: 'bytes', + }, + }, + }; + const indexPattern = { + id: '1234', + title: 'logstash-*', + fields: { + getByName: () => field, + filter: () => [field], + }, + } as any; + + const aggConfigs = new AggConfigs( + indexPattern, + [ + { + id: '1', + type: 'count', + schema: 'metric', + }, + { + id: '2', + type: metric.name, + schema: 'metric', + params, + }, + { + id: '3', + type: 'max', + params: { field: 'field' }, + schema: 'metric', + }, + ], + null + ); + + // Grab the aggConfig off the vis (we don't actually use the vis for anything else) + metricAgg = metric.provider; + aggConfig = aggConfigs.aggs[1]; + aggDsl = aggConfig.toDsl(aggConfigs); + }; + + it(`should return a label prefixed with ${metric.title} of`, () => { + init(); + + expect(metricAgg.makeLabel(aggConfig)).toEqual(`${metric.title} of Count`); + }); + + it(`should return a label ${metric.title} of max bytes`, () => { + init({ + metricAgg: 'custom', + customMetric: { + id: '1-orderAgg', + type: 'max', + params: { field: 'field' }, + schema: 'orderAgg', + }, + }); + expect(metricAgg.makeLabel(aggConfig)).toEqual(`${metric.title} of Max field`); + }); + + it(`should return a label prefixed with number of ${metric.title.toLowerCase()}`, () => { + init({ + metricAgg: 'custom', + customMetric: { + id: '2-orderAgg', + type: metric.name, + params: { + buckets_path: 'custom', + customMetric: { + id: '2-orderAgg-orderAgg', + type: 'count', + schema: 'orderAgg', + }, + }, + schema: 'orderAgg', + }, + }); + expect(metricAgg.makeLabel(aggConfig)).toEqual(`2. ${metric.title.toLowerCase()} of Count`); + }); + + it('should set parent aggs', () => { + init({ + metricAgg: 'custom', + customMetric: { + id: '2-metric', + type: 'max', + params: { field: 'field' }, + schema: 'orderAgg', + }, + }); + expect(aggDsl[metric.dslName || metric.name].buckets_path).toBe('2-metric'); + expect(aggDsl.parentAggs['2-metric'].max.field).toBe('field'); + }); + + it('should set nested parent aggs', () => { + init({ + metricAgg: 'custom', + customMetric: { + id: '2-metric', + type: metric.name, + params: { + buckets_path: 'custom', + customMetric: { + id: '2-metric-metric', + type: 'max', + params: { field: 'field' }, + schema: 'orderAgg', + }, + }, + schema: 'orderAgg', + }, + }); + expect(aggDsl[metric.dslName || metric.name].buckets_path).toBe('2-metric'); + expect(aggDsl.parentAggs['2-metric'][metric.dslName || metric.name].buckets_path).toBe( + '2-metric-metric' + ); + }); + + it('should have correct formatter', () => { + init({ + metricAgg: '3', + }); + expect(metricAgg.getFormat(aggConfig).type.id).toBe('bytes'); + }); + + it('should have correct customMetric nested formatter', () => { + init({ + metricAgg: 'custom', + customMetric: { + id: '2-metric', + type: metric.name, + params: { + buckets_path: 'custom', + customMetric: { + id: '2-metric-metric', + type: 'max', + params: { field: 'field' }, + schema: 'orderAgg', + }, + }, + schema: 'orderAgg', + }, + }); + expect(metricAgg.getFormat(aggConfig).type.id).toBe('bytes'); + }); + + it("should call modifyAggConfigOnSearchRequestStart for its customMetric's parameters", () => { + init({ + metricAgg: 'custom', + customMetric: { + id: '2-metric', + type: 'max', + params: { field: 'field' }, + schema: 'orderAgg', + }, + }); + + const searchSource: any = {}; + const customMetricSpy = sinon.spy(); + const customMetric = aggConfig.params.customMetric; + + // Attach a modifyAggConfigOnSearchRequestStart with a spy to the first parameter + customMetric.type.params[0].modifyAggConfigOnSearchRequestStart = customMetricSpy; + + aggConfig.type.params.forEach(param => { + param.modifyAggConfigOnSearchRequestStart(aggConfig, searchSource); + }); + expect(customMetricSpy.calledWith(customMetric, searchSource)).toBe(true); + }); + }); + }); +}); diff --git a/src/legacy/ui/public/agg_types/metrics/percentile_ranks.test.ts b/src/legacy/ui/public/agg_types/metrics/percentile_ranks.test.ts new file mode 100644 index 0000000000000..f3882ca57161f --- /dev/null +++ b/src/legacy/ui/public/agg_types/metrics/percentile_ranks.test.ts @@ -0,0 +1,75 @@ +/* + * 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 { IPercentileRanksAggConfig, percentileRanksMetricAgg } from './percentile_ranks'; +import { AggConfigs } from '../agg_configs'; +import { METRIC_TYPES } from './metric_agg_types'; + +jest.mock('ui/new_platform'); + +describe('AggTypesMetricsPercentileRanksProvider class', function() { + let aggConfigs: AggConfigs; + + beforeEach(() => { + const field = { + name: 'bytes', + }; + const indexPattern = { + id: '1234', + title: 'logstash-*', + fields: { + getByName: () => field, + filter: () => [field], + }, + } as any; + + aggConfigs = new AggConfigs( + indexPattern, + [ + { + id: METRIC_TYPES.PERCENTILE_RANKS, + type: METRIC_TYPES.PERCENTILE_RANKS, + schema: 'metric', + params: { + field: { + displayName: 'bytes', + format: { + convert: jest.fn(x => x), + }, + }, + customLabel: 'my custom field label', + values: [5000, 10000], + }, + }, + ], + null + ); + }); + + it('uses the custom label if it is set', function() { + const responseAggs: any = percentileRanksMetricAgg.getResponseAggs(aggConfigs + .aggs[0] as IPercentileRanksAggConfig); + + const percentileRankLabelFor5kBytes = responseAggs[0].makeLabel(); + const percentileRankLabelFor10kBytes = responseAggs[1].makeLabel(); + + expect(percentileRankLabelFor5kBytes).toBe('Percentile rank 5000 of "my custom field label"'); + expect(percentileRankLabelFor10kBytes).toBe('Percentile rank 10000 of "my custom field label"'); + }); +}); diff --git a/src/legacy/ui/public/agg_types/metrics/percentile_ranks.ts b/src/legacy/ui/public/agg_types/metrics/percentile_ranks.ts index 6c069f8b70d62..8b923092772db 100644 --- a/src/legacy/ui/public/agg_types/metrics/percentile_ranks.ts +++ b/src/legacy/ui/public/agg_types/metrics/percentile_ranks.ts @@ -20,7 +20,7 @@ import { i18n } from '@kbn/i18n'; import { PercentileRanksEditor } from '../../vis/editors/default/controls/percentile_ranks'; import { IMetricAggConfig, MetricAggType } from './metric_agg_type'; -import { getResponseAggConfigClass, IResponseAggConfig } from './get_response_agg_config_class'; +import { getResponseAggConfigClass, IResponseAggConfig } from './lib/get_response_agg_config_class'; import { getPercentileValue } from './percentiles_get_value'; import { METRIC_TYPES } from './metric_agg_types'; @@ -30,7 +30,7 @@ import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common'; // required by the values editor -type IPercentileRanksAggConfig = IResponseAggConfig; +export type IPercentileRanksAggConfig = IResponseAggConfig; const valueProps = { makeLabel(this: IPercentileRanksAggConfig) { diff --git a/src/legacy/ui/public/agg_types/metrics/percentiles.test.ts b/src/legacy/ui/public/agg_types/metrics/percentiles.test.ts new file mode 100644 index 0000000000000..1503f43b22dc3 --- /dev/null +++ b/src/legacy/ui/public/agg_types/metrics/percentiles.test.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 { IPercentileAggConfig, percentilesMetricAgg } from './percentiles'; +import { AggConfigs } from '../agg_configs'; +import { METRIC_TYPES } from './metric_agg_types'; + +jest.mock('ui/new_platform'); + +describe('AggTypesMetricsPercentilesProvider class', () => { + let aggConfigs: AggConfigs; + + beforeEach(() => { + const field = { + name: 'bytes', + }; + const indexPattern = { + id: '1234', + title: 'logstash-*', + fields: { + getByName: () => field, + filter: () => [field], + }, + } as any; + + aggConfigs = new AggConfigs( + indexPattern, + [ + { + id: METRIC_TYPES.PERCENTILES, + type: METRIC_TYPES.PERCENTILES, + schema: 'metric', + params: { + field: { + displayName: 'bytes', + format: { + convert: jest.fn(x => `${x}th`), + }, + }, + customLabel: 'prince', + percents: [95], + }, + }, + ], + null + ); + }); + + it('uses the custom label if it is set', () => { + const responseAggs: any = percentilesMetricAgg.getResponseAggs(aggConfigs + .aggs[0] as IPercentileAggConfig); + + const ninetyFifthPercentileLabel = responseAggs[0].makeLabel(); + + expect(ninetyFifthPercentileLabel).toBe('95th percentile of prince'); + }); +}); diff --git a/src/legacy/ui/public/agg_types/metrics/percentiles.ts b/src/legacy/ui/public/agg_types/metrics/percentiles.ts index 21cb3939d38f7..0ac0455468472 100644 --- a/src/legacy/ui/public/agg_types/metrics/percentiles.ts +++ b/src/legacy/ui/public/agg_types/metrics/percentiles.ts @@ -23,14 +23,14 @@ import { IMetricAggConfig, MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common'; -import { getResponseAggConfigClass, IResponseAggConfig } from './get_response_agg_config_class'; +import { getResponseAggConfigClass, IResponseAggConfig } from './lib/get_response_agg_config_class'; import { getPercentileValue } from './percentiles_get_value'; import { PercentilesEditor } from '../../vis/editors/default/controls/percentiles'; // @ts-ignore import { ordinalSuffix } from '../../utils/ordinal_suffix'; -type IPercentileAggConfig = IResponseAggConfig; +export type IPercentileAggConfig = IResponseAggConfig; const valueProps = { makeLabel(this: IPercentileAggConfig) { diff --git a/src/legacy/ui/public/agg_types/metrics/percentiles_get_value.ts b/src/legacy/ui/public/agg_types/metrics/percentiles_get_value.ts index 56e1f0456d62e..c357d7bb0a903 100644 --- a/src/legacy/ui/public/agg_types/metrics/percentiles_get_value.ts +++ b/src/legacy/ui/public/agg_types/metrics/percentiles_get_value.ts @@ -18,7 +18,7 @@ */ import { find } from 'lodash'; -import { IResponseAggConfig } from './get_response_agg_config_class'; +import { IResponseAggConfig } from './lib/get_response_agg_config_class'; export const getPercentileValue = ( agg: TAggConfig, diff --git a/src/legacy/ui/public/agg_types/metrics/sibling_pipeline.test.ts b/src/legacy/ui/public/agg_types/metrics/sibling_pipeline.test.ts new file mode 100644 index 0000000000000..a3381aca6f9e7 --- /dev/null +++ b/src/legacy/ui/public/agg_types/metrics/sibling_pipeline.test.ts @@ -0,0 +1,188 @@ +/* + * 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 { spy } from 'sinon'; +import { bucketSumMetricAgg } from './bucket_sum'; +import { bucketAvgMetricAgg } from './bucket_avg'; +import { bucketMinMetricAgg } from './bucket_min'; +import { bucketMaxMetricAgg } from './bucket_max'; + +import { AggConfigs } from 'ui/agg_types'; +import { IMetricAggConfig, MetricAggType } from 'ui/agg_types/metrics/metric_agg_type'; + +jest.mock('../../vis/editors/default/schemas', () => { + class MockedSchemas { + all = [{}]; + } + return { + Schemas: jest.fn().mockImplementation(() => new MockedSchemas()), + }; +}); + +jest.mock('../../vis/editors/default/controls/sub_metric', () => { + return { + SubMetricParamEditor() {}, + }; +}); + +jest.mock('ui/new_platform'); + +describe('sibling pipeline aggs', () => { + const metrics = [ + { name: 'sum_bucket', title: 'Overall Sum', provider: bucketSumMetricAgg }, + { name: 'avg_bucket', title: 'Overall Average', provider: bucketAvgMetricAgg }, + { name: 'min_bucket', title: 'Overall Min', provider: bucketMinMetricAgg }, + { name: 'max_bucket', title: 'Overall Max', provider: bucketMaxMetricAgg }, + ]; + + metrics.forEach(metric => { + describe(`${metric.title} metric`, () => { + let aggDsl: Record; + let metricAgg: MetricAggType; + let aggConfig: IMetricAggConfig; + + const init = (settings?: any) => { + const field = { + name: 'field', + format: { + type: { + id: 'bytes', + }, + }, + }; + const indexPattern = { + id: '1234', + title: 'logstash-*', + fields: { + getByName: () => field, + filter: () => [field], + }, + } as any; + + const aggConfigs = new AggConfigs( + indexPattern, + [ + { + id: '1', + type: 'count', + schema: 'metric', + }, + { + id: '2', + type: metric.name, + schema: 'metric', + params: settings || { + customMetric: { + id: '5', + type: 'count', + schema: 'metric', + }, + customBucket: { + id: '6', + type: 'date_histogram', + schema: 'bucket', + params: { field: 'field', interval: '10s' }, + }, + }, + }, + ], + null + ); + + // Grab the aggConfig off the vis (we don't actually use the vis for anything else) + metricAgg = metric.provider; + aggConfig = aggConfigs.aggs[1]; + aggDsl = aggConfig.toDsl(aggConfigs); + }; + + it(`should return a label prefixed with ${metric.title} of`, () => { + init(); + + expect(metricAgg.makeLabel(aggConfig)).toEqual(`${metric.title} of Count`); + }); + + it('should set parent aggs', function() { + init(); + + expect(aggDsl[metric.name].buckets_path).toBe('2-bucket>_count'); + expect(aggDsl.parentAggs['2-bucket'].date_histogram).not.toBeUndefined(); + }); + + it('should set nested parent aggs', () => { + init({ + customMetric: { + id: '5', + type: 'avg', + schema: 'metric', + params: { field: 'field' }, + }, + customBucket: { + id: '6', + type: 'date_histogram', + schema: 'bucket', + params: { field: 'field', interval: '10s' }, + }, + }); + + expect(aggDsl[metric.name].buckets_path).toBe('2-bucket>2-metric'); + expect(aggDsl.parentAggs['2-bucket'].date_histogram).not.toBeUndefined(); + expect(aggDsl.parentAggs['2-bucket'].aggs['2-metric'].avg.field).toEqual('field'); + }); + + it('should have correct formatter', () => { + init({ + customMetric: { + id: '5', + type: 'avg', + schema: 'metric', + params: { field: 'field' }, + }, + customBucket: { + id: '6', + type: 'date_histogram', + schema: 'bucket', + params: { field: 'field', interval: '10s' }, + }, + }); + + expect(metricAgg.getFormat(aggConfig).type.id).toBe('bytes'); + }); + + it("should call modifyAggConfigOnSearchRequestStart for nested aggs' parameters", () => { + init(); + + const searchSource: any = {}; + const customMetricSpy = spy(); + const customBucketSpy = spy(); + const { customMetric, customBucket } = aggConfig.params; + + // Attach a modifyAggConfigOnSearchRequestStart with a spy to the first parameter + customMetric.type.params[0].modifyAggConfigOnSearchRequestStart = customMetricSpy; + customBucket.type.params[0].modifyAggConfigOnSearchRequestStart = customBucketSpy; + + aggConfig.type.params.forEach(param => { + param.modifyAggConfigOnSearchRequestStart(aggConfig, searchSource); + }); + + expect(customMetricSpy.calledWith(customMetric, searchSource)).toBe(true); + expect(customBucketSpy.calledWith(customBucket, searchSource)).toBe(true); + }); + }); + }); +}); diff --git a/src/legacy/ui/public/agg_types/metrics/std_deviation.test.ts b/src/legacy/ui/public/agg_types/metrics/std_deviation.test.ts new file mode 100644 index 0000000000000..ca81e8daee449 --- /dev/null +++ b/src/legacy/ui/public/agg_types/metrics/std_deviation.test.ts @@ -0,0 +1,83 @@ +/* + * 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 { IStdDevAggConfig, stdDeviationMetricAgg } from './std_deviation'; +import { AggConfigs } from 'ui/agg_types'; +import { METRIC_TYPES } from 'ui/agg_types/metrics/metric_agg_types'; + +jest.mock('ui/new_platform'); + +describe('AggTypeMetricStandardDeviationProvider class', () => { + const getAggConfigs = (customLabel?: string) => { + const field = { + name: 'memory', + }; + const indexPattern = { + id: '1234', + title: 'logstash-*', + fields: { + getByName: () => field, + filter: () => [field], + }, + } as any; + + return new AggConfigs( + indexPattern, + [ + { + id: METRIC_TYPES.STD_DEV, + type: METRIC_TYPES.STD_DEV, + schema: 'metric', + params: { + field: { + displayName: 'memory', + }, + customLabel, + }, + }, + ], + null + ); + }; + + it('uses the custom label if it is set', () => { + const aggConfigs = getAggConfigs('custom label'); + const responseAggs: any = stdDeviationMetricAgg.getResponseAggs(aggConfigs + .aggs[0] as IStdDevAggConfig); + + const lowerStdDevLabel = responseAggs[0].makeLabel(); + const upperStdDevLabel = responseAggs[1].makeLabel(); + + expect(lowerStdDevLabel).toBe('Lower custom label'); + expect(upperStdDevLabel).toBe('Upper custom label'); + }); + + it('uses the default labels if custom label is not set', () => { + const aggConfigs = getAggConfigs(); + + const responseAggs: any = stdDeviationMetricAgg.getResponseAggs(aggConfigs + .aggs[0] as IStdDevAggConfig); + + const lowerStdDevLabel = responseAggs[0].makeLabel(); + const upperStdDevLabel = responseAggs[1].makeLabel(); + + expect(lowerStdDevLabel).toBe('Lower Standard Deviation of memory'); + expect(upperStdDevLabel).toBe('Upper Standard Deviation of memory'); + }); +}); diff --git a/src/legacy/ui/public/agg_types/metrics/std_deviation.ts b/src/legacy/ui/public/agg_types/metrics/std_deviation.ts index 89051f119c588..ebd5fceb9c751 100644 --- a/src/legacy/ui/public/agg_types/metrics/std_deviation.ts +++ b/src/legacy/ui/public/agg_types/metrics/std_deviation.ts @@ -21,7 +21,7 @@ import { get } from 'lodash'; import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; -import { getResponseAggConfigClass, IResponseAggConfig } from './get_response_agg_config_class'; +import { getResponseAggConfigClass, IResponseAggConfig } from './lib/get_response_agg_config_class'; import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common'; interface ValProp { @@ -29,7 +29,7 @@ interface ValProp { title: string; } -interface IStdDevAggConfig extends IResponseAggConfig { +export interface IStdDevAggConfig extends IResponseAggConfig { keyedDetails: (customLabel: string, fieldDisplayName?: string) => { [key: string]: ValProp }; valProp: () => ValProp; } diff --git a/src/legacy/ui/public/agg_types/metrics/top_hit.test.ts b/src/legacy/ui/public/agg_types/metrics/top_hit.test.ts new file mode 100644 index 0000000000000..4ed6fcdcf641b --- /dev/null +++ b/src/legacy/ui/public/agg_types/metrics/top_hit.test.ts @@ -0,0 +1,366 @@ +/* + * 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 { dropRight, last } from 'lodash'; +import { topHitMetricAgg } from './top_hit'; +import { AggConfigs } from 'ui/agg_types'; +import { IMetricAggConfig } from 'ui/agg_types/metrics/metric_agg_type'; +import { KBN_FIELD_TYPES } from '../../../../../plugins/data/common'; + +jest.mock('ui/new_platform'); + +describe('Top hit metric', () => { + let aggDsl: Record; + let aggConfig: IMetricAggConfig; + + const init = ({ + fieldName = 'field', + sortOrder = 'desc', + aggregate = 'concat', + readFromDocValues = false, + fieldType = KBN_FIELD_TYPES.NUMBER, + size = 1, + }: any) => { + const field = { + name: fieldName, + displayName: fieldName, + type: fieldType, + readFromDocValues, + format: { + type: { + id: 'bytes', + }, + }, + }; + + const params = { + size, + field: fieldName, + sortField: field, + sortOrder: { + value: sortOrder, + }, + aggregate: { + value: aggregate, + }, + }; + + const indexPattern = { + id: '1234', + title: 'logstash-*', + fields: { + getByName: () => field, + filter: () => [field], + }, + flattenHit: jest.fn(x => x!._source), + } as any; + + const aggConfigs = new AggConfigs( + indexPattern, + [ + { + id: '1', + type: 'top_hits', + schema: 'metric', + params, + }, + ], + null + ); + + // Grab the aggConfig off the vis (we don't actually use the vis for anything else) + aggConfig = aggConfigs.aggs[0]; + aggDsl = aggConfig.toDsl(aggConfigs); + }; + + it('should return a label prefixed with Last if sorting in descending order', () => { + init({ fieldName: 'bytes' }); + expect(topHitMetricAgg.makeLabel(aggConfig)).toEqual('Last bytes'); + }); + + it('should return a label prefixed with First if sorting in ascending order', () => { + init({ + fieldName: 'bytes', + sortOrder: 'asc', + }); + expect(topHitMetricAgg.makeLabel(aggConfig)).toEqual('First bytes'); + }); + + it('should request the _source field', () => { + init({ field: '_source' }); + expect(aggDsl.top_hits._source).toBeTruthy(); + expect(aggDsl.top_hits.docvalue_fields).toBeUndefined(); + }); + + it('requests both source and docvalues_fields for non-text aggregatable fields', () => { + init({ fieldName: 'bytes', readFromDocValues: true }); + expect(aggDsl.top_hits._source).toBe('bytes'); + expect(aggDsl.top_hits.docvalue_fields).toEqual([ + { field: 'bytes', format: 'use_field_mapping' }, + ]); + }); + + it('requests both source and docvalues_fields for date aggregatable fields', () => { + init({ fieldName: '@timestamp', readFromDocValues: true, fieldType: KBN_FIELD_TYPES.DATE }); + + expect(aggDsl.top_hits._source).toBe('@timestamp'); + expect(aggDsl.top_hits.docvalue_fields).toEqual([{ field: '@timestamp', format: 'date_time' }]); + }); + + it('requests just source for aggregatable text fields', () => { + init({ fieldName: 'machine.os' }); + expect(aggDsl.top_hits._source).toBe('machine.os'); + expect(aggDsl.top_hits.docvalue_fields).toBeUndefined(); + }); + + describe('try to get the value from the top hit', () => { + it('should return null if there is no hit', () => { + const bucket = { + '1': { + hits: { + hits: [], + }, + }, + }; + + init({ fieldName: '@tags' }); + expect(topHitMetricAgg.getValue(aggConfig, bucket)).toBe(null); + }); + // + it('should return undefined if the field does not appear in the source', () => { + const bucket = { + '1': { + hits: { + hits: [ + { + _source: { + bytes: 123, + }, + }, + ], + }, + }, + }; + + init({ fieldName: '@tags' }); + expect(topHitMetricAgg.getValue(aggConfig, bucket)).toBe(undefined); + }); + + it('should return the field value from the top hit', () => { + const bucket = { + '1': { + hits: { + hits: [ + { + _source: { + '@tags': 'aaa', + }, + }, + ], + }, + }, + }; + + init({ fieldName: '@tags' }); + expect(topHitMetricAgg.getValue(aggConfig, bucket)).toBe('aaa'); + }); + + it('should return the object if the field value is an object', () => { + const bucket = { + '1': { + hits: { + hits: [ + { + _source: { + '@tags': { + label: 'aaa', + }, + }, + }, + ], + }, + }, + }; + + init({ fieldName: '@tags' }); + + expect(topHitMetricAgg.getValue(aggConfig, bucket)).toEqual({ label: 'aaa' }); + }); + + it('should return an array if the field has more than one values', () => { + const bucket = { + '1': { + hits: { + hits: [ + { + _source: { + '@tags': ['aaa', 'bbb'], + }, + }, + ], + }, + }, + }; + + init({ fieldName: '@tags' }); + expect(topHitMetricAgg.getValue(aggConfig, bucket)).toEqual(['aaa', 'bbb']); + }); + + it('should return undefined if the field is not in the source nor in the doc_values field', () => { + const bucket = { + '1': { + hits: { + hits: [ + { + _source: { + bytes: 12345, + }, + fields: { + bytes: 12345, + }, + }, + ], + }, + }, + }; + + init({ fieldName: 'machine.os.raw', readFromDocValues: true }); + expect(topHitMetricAgg.getValue(aggConfig, bucket)).toBe(undefined); + }); + + describe('Multivalued field and first/last X docs', () => { + it('should return a label prefixed with Last X docs if sorting in descending order', () => { + init({ + fieldName: 'bytes', + size: 2, + }); + expect(topHitMetricAgg.makeLabel(aggConfig)).toEqual('Last 2 bytes'); + }); + + it('should return a label prefixed with First X docs if sorting in ascending order', () => { + init({ + fieldName: 'bytes', + size: 2, + sortOrder: 'asc', + }); + expect(topHitMetricAgg.makeLabel(aggConfig)).toEqual('First 2 bytes'); + }); + + [ + { + description: 'concat values with a comma', + type: 'concat', + data: [1, 2, 3], + result: [1, 2, 3], + }, + { + description: 'sum up the values', + type: 'sum', + data: [1, 2, 3], + result: 6, + }, + { + description: 'take the minimum value', + type: 'min', + data: [1, 2, 3], + result: 1, + }, + { + description: 'take the maximum value', + type: 'max', + data: [1, 2, 3], + result: 3, + }, + { + description: 'take the average value', + type: 'average', + data: [1, 2, 3], + result: 2, + }, + { + description: 'support null/undefined', + type: 'min', + data: [undefined, null], + result: null, + }, + { + description: 'support null/undefined', + type: 'max', + data: [undefined, null], + result: null, + }, + { + description: 'support null/undefined', + type: 'sum', + data: [undefined, null], + result: null, + }, + { + description: 'support null/undefined', + type: 'average', + data: [undefined, null], + result: null, + }, + ].forEach(agg => { + it(`should return the result of the ${agg.type} aggregation over the last doc - ${agg.description}`, () => { + const bucket = { + '1': { + hits: { + hits: [ + { + _source: { + bytes: agg.data, + }, + }, + ], + }, + }, + }; + + init({ fieldName: 'bytes', aggregate: agg.type }); + expect(topHitMetricAgg.getValue(aggConfig, bucket)).toEqual(agg.result); + }); + + it(`should return the result of the ${agg.type} aggregation over the last X docs - ${agg.description}`, () => { + const bucket = { + '1': { + hits: { + hits: [ + { + _source: { + bytes: dropRight(agg.data, 1), + }, + }, + { + _source: { + bytes: last(agg.data), + }, + }, + ], + }, + }, + }; + + init({ fieldName: 'bytes', aggregate: agg.type }); + expect(topHitMetricAgg.getValue(aggConfig, bucket)).toEqual(agg.result); + }); + }); + }); + }); +}); diff --git a/src/legacy/ui/public/autoload/settings.js b/src/legacy/ui/public/autoload/settings.js index c496839dda5d2..50ae67c052b93 100644 --- a/src/legacy/ui/public/autoload/settings.js +++ b/src/legacy/ui/public/autoload/settings.js @@ -17,34 +17,4 @@ * under the License. */ -/** - * Autoload this file if we want some of the top level settings applied to a plugin. - * Currently this file makes sure the following settings are applied globally: - * - dateFormat:tz (meaning the Kibana time zone will be used in your plugin) - * - dateFormat:dow (meaning the Kibana configured start of the week will be used in your plugin) - */ - -import moment from 'moment-timezone'; -import chrome from '../chrome'; - -function setDefaultTimezone(tz) { - moment.tz.setDefault(tz); -} - -function setStartDayOfWeek(day) { - const dow = moment.weekdays().indexOf(day); - moment.updateLocale(moment.locale(), { week: { dow } }); -} - -const uiSettings = chrome.getUiSettingsClient(); - -setDefaultTimezone(uiSettings.get('dateFormat:tz')); -setStartDayOfWeek(uiSettings.get('dateFormat:dow')); - -uiSettings.getUpdate$().subscribe(({ key, newValue }) => { - if (key === 'dateFormat:tz') { - setDefaultTimezone(newValue); - } else if (key === 'dateFormat:dow') { - setStartDayOfWeek(newValue); - } -}); +/** Left intentionally empty to avoid breaking plugins that import this file during the NP migration */ diff --git a/src/legacy/ui/public/autoload/styles.js b/src/legacy/ui/public/autoload/styles.js index 540f65edbf4d6..c623acca07b01 100644 --- a/src/legacy/ui/public/autoload/styles.js +++ b/src/legacy/ui/public/autoload/styles.js @@ -17,9 +17,4 @@ * under the License. */ -// All Kibana styles inside of the /styles dir -const context = require.context('../styles', false, /[\/\\](?!mixins|variables|_|\.|bootstrap_(light|dark))[^\/\\]+\.less/); -context.keys().forEach(key => context(key)); - -// manually require non-less files -import '../styles/disable_animations'; +import 'ui/styles/font_awesome.less'; diff --git a/src/legacy/ui/public/chrome/api/base_path.test.mocks.ts b/src/legacy/ui/public/chrome/api/base_path.test.mocks.ts index c362b1709fba6..f2c5fd5734b10 100644 --- a/src/legacy/ui/public/chrome/api/base_path.test.mocks.ts +++ b/src/legacy/ui/public/chrome/api/base_path.test.mocks.ts @@ -19,7 +19,7 @@ import { httpServiceMock } from '../../../../../core/public/mocks'; -export const newPlatformHttp = httpServiceMock.createSetupContract(); +const newPlatformHttp = httpServiceMock.createSetupContract({ basePath: 'npBasePath' }); jest.doMock('ui/new_platform', () => ({ npSetup: { core: { http: newPlatformHttp }, diff --git a/src/legacy/ui/public/chrome/api/base_path.test.ts b/src/legacy/ui/public/chrome/api/base_path.test.ts index 812635ba36483..d3cfc6a3168a8 100644 --- a/src/legacy/ui/public/chrome/api/base_path.test.ts +++ b/src/legacy/ui/public/chrome/api/base_path.test.ts @@ -16,8 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - -import { newPlatformHttp } from './base_path.test.mocks'; +import './base_path.test.mocks'; import { initChromeBasePathApi } from './base_path'; function initChrome() { @@ -26,10 +25,6 @@ function initChrome() { return chrome; } -newPlatformHttp.basePath.get.mockImplementation(() => 'gotBasePath'); -newPlatformHttp.basePath.prepend.mockImplementation(() => 'addedToPath'); -newPlatformHttp.basePath.remove.mockImplementation(() => 'removedFromPath'); - beforeEach(() => { jest.clearAllMocks(); }); @@ -37,29 +32,20 @@ beforeEach(() => { describe('#getBasePath()', () => { it('proxies to newPlatformHttp.basePath.get()', () => { const chrome = initChrome(); - expect(newPlatformHttp.basePath.prepend).not.toHaveBeenCalled(); - expect(chrome.getBasePath()).toBe('gotBasePath'); - expect(newPlatformHttp.basePath.get).toHaveBeenCalledTimes(1); - expect(newPlatformHttp.basePath.get).toHaveBeenCalledWith(); + expect(chrome.getBasePath()).toBe('npBasePath'); }); }); describe('#addBasePath()', () => { it('proxies to newPlatformHttp.basePath.prepend(path)', () => { const chrome = initChrome(); - expect(newPlatformHttp.basePath.prepend).not.toHaveBeenCalled(); - expect(chrome.addBasePath('foo/bar')).toBe('addedToPath'); - expect(newPlatformHttp.basePath.prepend).toHaveBeenCalledTimes(1); - expect(newPlatformHttp.basePath.prepend).toHaveBeenCalledWith('foo/bar'); + expect(chrome.addBasePath('/foo/bar')).toBe('npBasePath/foo/bar'); }); }); describe('#removeBasePath', () => { it('proxies to newPlatformBasePath.basePath.remove(path)', () => { const chrome = initChrome(); - expect(newPlatformHttp.basePath.remove).not.toHaveBeenCalled(); - expect(chrome.removeBasePath('foo/bar')).toBe('removedFromPath'); - expect(newPlatformHttp.basePath.remove).toHaveBeenCalledTimes(1); - expect(newPlatformHttp.basePath.remove).toHaveBeenCalledWith('foo/bar'); + expect(chrome.removeBasePath('npBasePath/foo/bar')).toBe('/foo/bar'); }); }); 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 b4c3700e36f68..fa8b83f755957 100644 --- a/src/legacy/ui/public/doc_title/__tests__/doc_title.js +++ b/src/legacy/ui/public/doc_title/__tests__/doc_title.js @@ -20,7 +20,8 @@ import sinon from 'sinon'; import expect from '@kbn/expect'; import ngMock from 'ng_mock'; -import { setBaseTitle, docTitle } from '../doc_title'; +import { docTitle } from '../doc_title'; +import { npStart } from '../../new_platform'; describe('docTitle Service', function () { let initialDocTitle; @@ -30,20 +31,24 @@ describe('docTitle Service', function () { beforeEach(function () { initialDocTitle = document.title; document.title = MAIN_TITLE; - setBaseTitle(MAIN_TITLE); + npStart.core.chrome.docTitle.__legacy.setBaseTitle(MAIN_TITLE); }); afterEach(function () { document.title = initialDocTitle; - setBaseTitle(initialDocTitle); + npStart.core.chrome.docTitle.__legacy.setBaseTitle(initialDocTitle); }); - beforeEach(ngMock.module('kibana', function ($provide) { - $provide.decorator('$rootScope', decorateWithSpy('$on')); - })); + beforeEach( + ngMock.module('kibana', function ($provide) { + $provide.decorator('$rootScope', decorateWithSpy('$on')); + }) + ); - beforeEach(ngMock.inject(function ($injector) { - $rootScope = $injector.get('$rootScope'); - })); + beforeEach( + ngMock.inject(function ($injector) { + $rootScope = $injector.get('$rootScope'); + }) + ); describe('setup', function () { it('resets the title when a route change begins', function () { @@ -60,13 +65,11 @@ describe('docTitle Service', function () { }); describe('#reset', function () { - it('clears the internal state, next update() will write the default', function () { + it('clears the internal state', function () { docTitle.change('some title'); - docTitle.update(); expect(document.title).to.be('some title - ' + MAIN_TITLE); docTitle.reset(); - docTitle.update(); expect(document.title).to.be(MAIN_TITLE); }); }); @@ -77,12 +80,6 @@ describe('docTitle Service', function () { docTitle.change('some secondary title'); expect(document.title).to.be('some secondary title - ' + MAIN_TITLE); }); - - it('will write just the first param if the second param is true', function () { - expect(document.title).to.be(MAIN_TITLE); - docTitle.change('entire name', true); - expect(document.title).to.be('entire name'); - }); }); function decorateWithSpy(prop) { @@ -91,5 +88,4 @@ describe('docTitle Service', function () { 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 3692fd71f06cc..0edc55a0ac366 100644 --- a/src/legacy/ui/public/doc_title/doc_title.js +++ b/src/legacy/ui/public/doc_title/doc_title.js @@ -17,53 +17,28 @@ * under the License. */ -import _ from 'lodash'; +import { isArray } from 'lodash'; import { uiModules } from '../modules'; +import { npStart } from '../new_platform'; -let baseTitle = document.title; +const npDocTitle = () => npStart.core.chrome.docTitle; -// for karma test -export function setBaseTitle(str) { - baseTitle = str; -} - -let lastChange; - -function render() { - lastChange = lastChange || []; - - const parts = [lastChange[0]]; - - if (!lastChange[1]) parts.push(baseTitle); - - return _(parts).flattenDeep().compact().join(' - '); -} - -function change(title, complete) { - lastChange = [title, complete]; - update(); +function change(title) { + npDocTitle().change(isArray(title) ? title : [title]); } function reset() { - lastChange = null; -} - -function update() { - document.title = render(); + npDocTitle().reset(); } export const docTitle = { - render, change, reset, - update, }; uiModules.get('kibana') .run(function ($rootScope) { // always bind to the route events $rootScope.$on('$routeChangeStart', docTitle.reset); - $rootScope.$on('$routeChangeError', docTitle.update); - $rootScope.$on('$routeChangeSuccess', docTitle.update); }); diff --git a/src/legacy/ui/public/kbn_top_nav/kbn_top_nav.js b/src/legacy/ui/public/kbn_top_nav/kbn_top_nav.js index 79365eb5cf1cc..9c4cee6b05db0 100644 --- a/src/legacy/ui/public/kbn_top_nav/kbn_top_nav.js +++ b/src/legacy/ui/public/kbn_top_nav/kbn_top_nav.js @@ -20,11 +20,11 @@ import 'ngreact'; import { wrapInI18nContext } from 'ui/i18n'; import { uiModules } from 'ui/modules'; -import { TopNavMenu } from '../../../core_plugins/kibana_react/public'; +import { start as navigation } from '../../../core_plugins/navigation/public/legacy'; const module = uiModules.get('kibana'); -module.directive('kbnTopNav', () => { +export function createTopNavDirective() { return { restrict: 'E', template: '', @@ -71,9 +71,12 @@ module.directive('kbnTopNav', () => { return linkFn; } }; -}); +} -module.directive('kbnTopNavHelper', (reactDirective) => { +module.directive('kbnTopNav', createTopNavDirective); + +export function createTopNavHelper(reactDirective) { + const { TopNavMenu } = navigation.ui; return reactDirective( wrapInI18nContext(TopNavMenu), [ @@ -113,4 +116,6 @@ module.directive('kbnTopNavHelper', (reactDirective) => { 'showAutoRefreshOnly', ], ); -}); +} + +module.directive('kbnTopNavHelper', createTopNavHelper); diff --git a/src/legacy/ui/public/legacy_compat/angular_config.tsx b/src/legacy/ui/public/legacy_compat/angular_config.tsx index 8eac31e24530c..785b0345aa999 100644 --- a/src/legacy/ui/public/legacy_compat/angular_config.tsx +++ b/src/legacy/ui/public/legacy_compat/angular_config.tsx @@ -286,14 +286,12 @@ const $setupHelpExtensionAutoClear = (newPlatform: CoreStart) => ( const $setupUrlOverflowHandling = (newPlatform: CoreStart) => ( $location: ILocationService, - $rootScope: IRootScopeService, - Private: any, - config: any + $rootScope: IRootScopeService ) => { const urlOverflow = new UrlOverflowService(); const check = () => { // disable long url checks when storing state in session storage - if (config.get('state:storeInSessionStorage')) { + if (newPlatform.uiSettings.get('state:storeInSessionStorage')) { return; } diff --git a/src/legacy/ui/public/modals/confirm_modal.js b/src/legacy/ui/public/modals/confirm_modal.js index 6d5abfca64aaf..9c3f46da4e927 100644 --- a/src/legacy/ui/public/modals/confirm_modal.js +++ b/src/legacy/ui/public/modals/confirm_modal.js @@ -36,16 +36,7 @@ export const ConfirmationButtonTypes = { CANCEL: CANCEL_BUTTON }; -/** - * @typedef {Object} ConfirmModalOptions - * @property {String} confirmButtonText - * @property {String=} cancelButtonText - * @property {function} onConfirm - * @property {function=} onCancel - * @property {String=} title - If given, shows a title on the confirm modal. - */ - -module.factory('confirmModal', function ($rootScope, $compile) { +export function confirmModalFactory($rootScope, $compile) { let modalPopover; const confirmQueue = []; @@ -114,4 +105,15 @@ module.factory('confirmModal', function ($rootScope, $compile) { } } }; -}); +} + +/** + * @typedef {Object} ConfirmModalOptions + * @property {String} confirmButtonText + * @property {String=} cancelButtonText + * @property {function} onConfirm + * @property {function=} onCancel + * @property {String=} title - If given, shows a title on the confirm modal. + */ + +module.factory('confirmModal', confirmModalFactory); 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 ae5c0a83bd781..20ea9c9141aca 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 @@ -96,6 +96,10 @@ export const npStart = { export function __setup__(coreSetup) { npSetup.core = coreSetup; + + // no-op application register calls (this is overwritten to + // bootstrap an LP plugin outside of tests) + npSetup.core.application.register = () => {}; } export function __start__(coreStart) { diff --git a/src/legacy/ui/public/new_platform/new_platform.ts b/src/legacy/ui/public/new_platform/new_platform.ts index b86f9cde0125c..0c7b28e7da3df 100644 --- a/src/legacy/ui/public/new_platform/new_platform.ts +++ b/src/legacy/ui/public/new_platform/new_platform.ts @@ -28,11 +28,16 @@ import { Start as InspectorStart, } from '../../../../plugins/inspector/public'; import { EuiUtilsStart } from '../../../../plugins/eui_utils/public'; +import { + FeatureCatalogueSetup, + FeatureCatalogueStart, +} from '../../../../plugins/feature_catalogue/public'; export interface PluginsSetup { data: ReturnType; embeddable: EmbeddableSetup; expressions: ReturnType; + feature_catalogue: FeatureCatalogueSetup; inspector: InspectorSetup; uiActions: IUiActionsSetup; } @@ -42,6 +47,7 @@ export interface PluginsStart { embeddable: EmbeddableStart; eui_utils: EuiUtilsStart; expressions: ReturnType; + feature_catalogue: FeatureCatalogueStart; inspector: InspectorStart; uiActions: IUiActionsStart; } diff --git a/src/legacy/ui/public/private/private.js b/src/legacy/ui/public/private/private.js index ef5c59c21dd7a..6257c12ecf696 100644 --- a/src/legacy/ui/public/private/private.js +++ b/src/legacy/ui/public/private/private.js @@ -108,98 +108,100 @@ function name(fn) { return fn.name || fn.toString().split('\n').shift(); } -uiModules.get('kibana/private') - .provider('Private', function () { - const provider = this; - - // one cache/swaps per Provider - const cache = {}; - const swaps = {}; +export function PrivateProvider() { + const provider = this; - // return the uniq id for this function - function identify(fn) { - if (typeof fn !== 'function') { - throw new TypeError('Expected private module "' + fn + '" to be a function'); - } + // one cache/swaps per Provider + const cache = {}; + const swaps = {}; - if (fn.$$id) return fn.$$id; - else return (fn.$$id = nextId()); + // return the uniq id for this function + function identify(fn) { + if (typeof fn !== 'function') { + throw new TypeError('Expected private module "' + fn + '" to be a function'); } - provider.stub = function (fn, instance) { - cache[identify(fn)] = instance; - return instance; - }; + if (fn.$$id) return fn.$$id; + else return (fn.$$id = nextId()); + } - provider.swap = function (fn, prov) { - const id = identify(fn); - swaps[id] = prov; - }; + provider.stub = function (fn, instance) { + cache[identify(fn)] = instance; + return instance; + }; - provider.$get = ['$injector', function PrivateFactory($injector) { + provider.swap = function (fn, prov) { + const id = identify(fn); + swaps[id] = prov; + }; - // prevent circular deps by tracking where we came from - const privPath = []; - const pathToString = function () { - return privPath.map(name).join(' -> '); - }; + provider.$get = ['$injector', function PrivateFactory($injector) { - // call a private provider and return the instance it creates - function instantiate(prov, locals) { - if (~privPath.indexOf(prov)) { - throw new Error( - 'Circular reference to "' + name(prov) + '"' + - ' found while resolving private deps: ' + pathToString() - ); - } + // prevent circular deps by tracking where we came from + const privPath = []; + const pathToString = function () { + return privPath.map(name).join(' -> '); + }; - privPath.push(prov); + // call a private provider and return the instance it creates + function instantiate(prov, locals) { + if (~privPath.indexOf(prov)) { + throw new Error( + 'Circular reference to "' + name(prov) + '"' + + ' found while resolving private deps: ' + pathToString() + ); + } - const context = {}; - let instance = $injector.invoke(prov, context, locals); - if (!_.isObject(instance)) instance = context; + privPath.push(prov); - privPath.pop(); - return instance; - } + const context = {}; + let instance = $injector.invoke(prov, context, locals); + if (!_.isObject(instance)) instance = context; - // retrieve an instance from cache or create and store on - function get(id, prov, $delegateId, $delegateProv) { - if (cache[id]) return cache[id]; + privPath.pop(); + return instance; + } - let instance; + // retrieve an instance from cache or create and store on + function get(id, prov, $delegateId, $delegateProv) { + if (cache[id]) return cache[id]; - if ($delegateId != null && $delegateProv != null) { - instance = instantiate(prov, { - $decorate: _.partial(get, $delegateId, $delegateProv) - }); - } else { - instance = instantiate(prov); - } + let instance; - return (cache[id] = instance); + if ($delegateId != null && $delegateProv != null) { + instance = instantiate(prov, { + $decorate: _.partial(get, $delegateId, $delegateProv) + }); + } else { + instance = instantiate(prov); } - // main api, get the appropriate instance for a provider - function Private(prov) { - let id = identify(prov); - let $delegateId; - let $delegateProv; + return (cache[id] = instance); + } - if (swaps[id]) { - $delegateId = id; - $delegateProv = prov; + // main api, get the appropriate instance for a provider + function Private(prov) { + let id = identify(prov); + let $delegateId; + let $delegateProv; - prov = swaps[$delegateId]; - id = identify(prov); - } + if (swaps[id]) { + $delegateId = id; + $delegateProv = prov; - return get(id, prov, $delegateId, $delegateProv); + prov = swaps[$delegateId]; + id = identify(prov); } - Private.stub = provider.stub; - Private.swap = provider.swap; + return get(id, prov, $delegateId, $delegateProv); + } + + Private.stub = provider.stub; + Private.swap = provider.swap; + + return Private; + }]; +} - return Private; - }]; - }); +uiModules.get('kibana/private') + .provider('Private', PrivateProvider); diff --git a/src/legacy/ui/public/promises/promises.js b/src/legacy/ui/public/promises/promises.js index 99c9a11be7431..af8a5081e0c55 100644 --- a/src/legacy/ui/public/promises/promises.js +++ b/src/legacy/ui/public/promises/promises.js @@ -22,9 +22,7 @@ import { uiModules } from '../modules'; const module = uiModules.get('kibana'); -// Provides a tiny subset of the excellent API from -// bluebird, reimplemented using the $q service -module.service('Promise', function ($q, $timeout) { +export function PromiseServiceCreator($q, $timeout) { function Promise(fn) { if (typeof this === 'undefined') throw new Error('Promise constructor must be called with "new"'); @@ -122,4 +120,8 @@ module.service('Promise', function ($q, $timeout) { }; return Promise; -}); +} + +// Provides a tiny subset of the excellent API from +// bluebird, reimplemented using the $q service +module.service('Promise', PromiseServiceCreator); diff --git a/src/legacy/ui/public/registry/feature_catalogue.js b/src/legacy/ui/public/registry/feature_catalogue.js index 0eb8a4a334521..8905a15106953 100644 --- a/src/legacy/ui/public/registry/feature_catalogue.js +++ b/src/legacy/ui/public/registry/feature_catalogue.js @@ -19,6 +19,7 @@ import { uiRegistry } from './_registry'; import { capabilities } from '../capabilities'; +export { FeatureCatalogueCategory } from '../../../../plugins/feature_catalogue/public'; export const FeatureCatalogueRegistryProvider = uiRegistry({ name: 'featureCatalogue', @@ -30,9 +31,3 @@ export const FeatureCatalogueRegistryProvider = uiRegistry({ return !isDisabledViaCapabilities && Object.keys(featureCatalogItem).length > 0; } }); - -export const FeatureCatalogueCategory = { - ADMIN: 'admin', - DATA: 'data', - OTHER: 'other' -}; diff --git a/src/legacy/ui/public/saved_objects/show_saved_object_save_modal.tsx b/src/legacy/ui/public/saved_objects/show_saved_object_save_modal.tsx index 6aea3c72e0c34..3c691c692948a 100644 --- a/src/legacy/ui/public/saved_objects/show_saved_object_save_modal.tsx +++ b/src/legacy/ui/public/saved_objects/show_saved_object_save_modal.tsx @@ -34,7 +34,7 @@ function isSuccess(result: SaveResult): result is { id?: string } { return 'id' in result; } -interface MinimalSaveModalProps { +export interface MinimalSaveModalProps { onSave: (...args: any[]) => Promise; onClose: () => void; } diff --git a/src/legacy/ui/public/state_management/config_provider.js b/src/legacy/ui/public/state_management/config_provider.js index 090210cc8723e..ec770e7fef6ca 100644 --- a/src/legacy/ui/public/state_management/config_provider.js +++ b/src/legacy/ui/public/state_management/config_provider.js @@ -25,21 +25,23 @@ import { uiModules } from '../modules'; -uiModules.get('kibana/state_management') - .provider('stateManagementConfig', class StateManagementConfigProvider { - _enabled = true +export class StateManagementConfigProvider { + _enabled = true + + $get(/* inject stuff */) { + return { + enabled: this._enabled, + }; + } - $get(/* inject stuff */) { - return { - enabled: this._enabled, - }; - } + disable() { + this._enabled = false; + } - disable() { - this._enabled = false; - } + enable() { + this._enabled = true; + } +} - enable() { - this._enabled = true; - } - }); +uiModules.get('kibana/state_management') + .provider('stateManagementConfig', StateManagementConfigProvider); diff --git a/src/legacy/ui/public/styles/disable_animations/disable_animations.js b/src/legacy/ui/public/styles/disable_animations/disable_animations.js deleted file mode 100644 index c70aaa2165691..0000000000000 --- a/src/legacy/ui/public/styles/disable_animations/disable_animations.js +++ /dev/null @@ -1,46 +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 chrome from 'ui/chrome'; -import disableAnimationsCss from '!!raw-loader!./disable_animations.css'; - -const uiSettings = chrome.getUiSettingsClient(); - -// rather than silently ignore when the style element is missing in the tests -// like ui/theme does, we automatically create a style tag because ordering doesn't -// really matter for these styles, they should really take top priority because -// they all use `!important`, and we don't want to silently ignore the possibility -// of accidentally removing the style element from the chrome template. -const styleElement = document.createElement('style'); -styleElement.setAttribute('id', 'disableAnimationsCss'); -document.head.appendChild(styleElement); - -function updateStyleSheet() { - styleElement.textContent = uiSettings.get('accessibility:disableAnimations') - ? disableAnimationsCss - : ''; -} - -updateStyleSheet(); -uiSettings.getUpdate$().subscribe(({ key }) => { - if (key === 'accessibility:disableAnimations') { - updateStyleSheet(); - } -}); - diff --git a/src/legacy/ui/public/vis/editors/default/components/agg_params.tsx b/src/legacy/ui/public/vis/editors/default/components/agg_params.tsx index 6513c9211f18b..4f4c0bda6520a 100644 --- a/src/legacy/ui/public/vis/editors/default/components/agg_params.tsx +++ b/src/legacy/ui/public/vis/editors/default/components/agg_params.tsx @@ -21,9 +21,10 @@ import React, { useReducer, useEffect, useMemo } from 'react'; import { EuiForm, EuiAccordion, EuiSpacer, EuiFormRow } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { aggTypes, AggType, AggParam } from 'ui/agg_types'; +import { VisState } from 'ui/vis'; +import { aggTypes, AggType, AggParam, AggConfig } from 'ui/agg_types/'; import { IndexPattern } from 'ui/index_patterns'; -import { AggConfig, VisState } from '../../..'; + import { DefaultEditorAggSelect } from './agg_select'; import { DefaultEditorAggParam } from './agg_param'; import { diff --git a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/__snapshots__/number_list.test.tsx.snap b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/__snapshots__/number_list.test.tsx.snap new file mode 100644 index 0000000000000..ab192e6fd3cbb --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/__snapshots__/number_list.test.tsx.snap @@ -0,0 +1,77 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`NumberList should be rendered with default set of props 1`] = ` + + + + + + + + + + + +`; diff --git a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/__snapshots__/number_row.test.tsx.snap b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/__snapshots__/number_row.test.tsx.snap new file mode 100644 index 0000000000000..3882425594cf3 --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/__snapshots__/number_row.test.tsx.snap @@ -0,0 +1,38 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`NumberRow should be rendered with default set of props 1`] = ` + + + + + + + + +`; diff --git a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/number_list.test.tsx b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/number_list.test.tsx new file mode 100644 index 0000000000000..3faf164c365d9 --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/number_list.test.tsx @@ -0,0 +1,147 @@ +/* + * 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 { shallow } from 'enzyme'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; + +import { NumberList, NumberListProps } from './number_list'; +import { NumberRow } from './number_row'; + +jest.mock('./number_row', () => ({ + NumberRow: () => 'NumberRow', +})); + +jest.mock('@elastic/eui', () => ({ + htmlIdGenerator: jest.fn(() => { + let counter = 1; + return () => `12${counter++}`; + }), + EuiSpacer: require.requireActual('@elastic/eui').EuiSpacer, + EuiFlexItem: require.requireActual('@elastic/eui').EuiFlexItem, + EuiButtonEmpty: require.requireActual('@elastic/eui').EuiButtonEmpty, + EuiFormErrorText: require.requireActual('@elastic/eui').EuiFormErrorText, +})); + +describe('NumberList', () => { + let defaultProps: NumberListProps; + + beforeEach(() => { + defaultProps = { + labelledbyId: 'numberList', + numberArray: [1, 2], + range: '[1, 10]', + showValidation: false, + unitName: 'value', + onChange: jest.fn(), + setTouched: jest.fn(), + setValidity: jest.fn(), + }; + }); + + test('should be rendered with default set of props', () => { + const comp = shallow(); + + expect(comp).toMatchSnapshot(); + }); + + test('should show an order error', () => { + defaultProps.numberArray = [3, 1]; + defaultProps.showValidation = true; + const comp = mountWithIntl(); + + expect(comp.find('EuiFormErrorText').length).toBe(1); + }); + + test('should set validity as true', () => { + mountWithIntl(); + + expect(defaultProps.setValidity).lastCalledWith(true); + }); + + test('should set validity as false when the order is invalid', () => { + defaultProps.numberArray = [3, 2]; + const comp = mountWithIntl(); + + expect(defaultProps.setValidity).lastCalledWith(false); + + comp.setProps({ numberArray: [1, 2] }); + expect(defaultProps.setValidity).lastCalledWith(true); + }); + + test('should set validity as false when there is an empty field', () => { + defaultProps.numberArray = [1, 2]; + const comp = mountWithIntl(); + + comp.setProps({ numberArray: [1, undefined] }); + expect(defaultProps.setValidity).lastCalledWith(false); + }); + + test('should set 0 when number array is empty', () => { + defaultProps.numberArray = []; + mountWithIntl(); + + expect(defaultProps.onChange).lastCalledWith([0]); + }); + + test('should add a number', () => { + const comp = shallow(); + comp.find('EuiButtonEmpty').simulate('click'); + + expect(defaultProps.onChange).lastCalledWith([1, 2, 3]); + }); + + test('should remove a number', () => { + const comp = shallow(); + const row = comp.find(NumberRow).first(); + row.prop('onDelete')(row.prop('model').id); + + expect(defaultProps.onChange).lastCalledWith([2]); + }); + + test('should disable remove button if there is one number', () => { + defaultProps.numberArray = [1]; + const comp = shallow(); + + expect( + comp + .find(NumberRow) + .first() + .prop('disableDelete') + ).toEqual(true); + }); + + test('should change value', () => { + const comp = shallow(); + const row = comp.find(NumberRow).first(); + row.prop('onChange')({ id: row.prop('model').id, value: '3' }); + + expect(defaultProps.onChange).lastCalledWith([3, 2]); + }); + + test('should call setTouched', () => { + const comp = shallow(); + comp + .find(NumberRow) + .first() + .prop('onBlur')(); + + expect(defaultProps.setTouched).toBeCalled(); + }); +}); diff --git a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/number_list.tsx b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/number_list.tsx index 54040814028f7..4aae217c1bc8d 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/number_list.tsx +++ b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/number_list.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React, { Fragment, useState, useEffect } from 'react'; +import React, { Fragment, useState, useEffect, useMemo, useCallback } from 'react'; import { EuiSpacer, EuiButtonEmpty, EuiFlexItem, EuiFormErrorText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -34,16 +34,15 @@ import { getUpdatedModels, hasInvalidValues, } from './utils'; +import { useValidation } from '../../agg_utils'; -interface NumberListProps { +export interface NumberListProps { labelledbyId: string; numberArray: Array; range?: string; showValidation: boolean; unitName: string; validateAscendingOrder?: boolean; - onBlur?(): void; - onFocus?(): void; onChange(list: Array): void; setTouched(): void; setValidity(isValid: boolean): void; @@ -56,81 +55,84 @@ function NumberList({ showValidation, unitName, validateAscendingOrder = true, - onBlur, - onFocus, onChange, setTouched, setValidity, }: NumberListProps) { - const numberRange = getRange(range); + const numberRange = useMemo(() => getRange(range), [range]); const [models, setModels] = useState(getInitModelList(numberArray)); const [ascendingError, setAscendingError] = useState(EMPTY_STRING); - // responsible for discarding changes + // set up validity for each model useEffect(() => { - const updatedModels = getUpdatedModels(numberArray, models, numberRange); + let id: number | undefined; if (validateAscendingOrder) { - const isOrderValid = validateOrder(updatedModels); + const { isValidOrder, modelIndex } = validateOrder(numberArray); + id = isValidOrder ? undefined : modelIndex; setAscendingError( - isOrderValid - ? i18n.translate('common.ui.aggTypes.numberList.invalidAscOrderErrorMessage', { + isValidOrder + ? EMPTY_STRING + : i18n.translate('common.ui.aggTypes.numberList.invalidAscOrderErrorMessage', { defaultMessage: 'The values should be in ascending order.', }) - : EMPTY_STRING ); } - setModels(updatedModels); - }, [numberArray]); + setModels(state => getUpdatedModels(numberArray, state, numberRange, id)); + }, [numberArray, numberRange, validateAscendingOrder]); + // responsible for setting up an initial value ([0]) when there is no default value useEffect(() => { - setValidity(!hasInvalidValues(models)); - }, [models]); - - // resposible for setting up an initial value ([0]) when there is no default value - useEffect(() => { - onChange(models.map(({ value }) => (value === EMPTY_STRING ? undefined : value))); + if (!numberArray.length) { + onChange([models[0].value as number]); + } }, []); - const onChangeValue = ({ id, value }: { id: string; value: string }) => { - const parsedValue = parse(value); - const { isValid, errors } = validateValue(parsedValue, numberRange); - setValidity(isValid); + const isValid = !hasInvalidValues(models); + useValidation(setValidity, isValid); - const currentModel = models.find(model => model.id === id); - if (currentModel) { - currentModel.value = parsedValue; - currentModel.isInvalid = !isValid; - currentModel.errors = errors; - } + const onUpdate = useCallback( + (modelList: NumberRowModel[]) => { + setModels(modelList); + onChange(modelList.map(({ value }) => (value === EMPTY_STRING ? undefined : value))); + }, + [onChange] + ); - onUpdate(models); - }; + const onChangeValue = useCallback( + ({ id, value }: { id: string; value: string }) => { + const parsedValue = parse(value); + + onUpdate( + models.map(model => { + if (model.id === id) { + const { isInvalid, error } = validateValue(parsedValue, numberRange); + return { + id, + value: parsedValue, + isInvalid, + error, + }; + } + return model; + }) + ); + }, + [numberRange, models, onUpdate] + ); // Add an item to the end of the list - const onAdd = () => { + const onAdd = useCallback(() => { const newArray = [...models, getNextModel(models, numberRange)]; onUpdate(newArray); - }; - - const onDelete = (id: string) => { - const newArray = models.filter(model => model.id !== id); - onUpdate(newArray); - }; - - const onBlurFn = (model: NumberRowModel) => { - if (model.value === EMPTY_STRING) { - model.isInvalid = true; - } - setTouched(); - if (onBlur) { - onBlur(); - } - }; - - const onUpdate = (modelList: NumberRowModel[]) => { - setModels(modelList); - onChange(modelList.map(({ value }) => (value === EMPTY_STRING ? undefined : value))); - }; + }, [models, numberRange, onUpdate]); + + const onDelete = useCallback( + (id: string) => { + const newArray = models.filter(model => model.id !== id); + onUpdate(newArray); + }, + [models, onUpdate] + ); return ( <> @@ -143,13 +145,12 @@ function NumberList({ labelledbyId={labelledbyId} range={numberRange} onDelete={onDelete} - onFocus={onFocus} onChange={onChangeValue} - onBlur={() => onBlurFn(model)} + onBlur={setTouched} autoFocus={models.length !== 1 && arrayIndex === models.length - 1} /> - {showValidation && model.isInvalid && model.errors && model.errors.length > 0 && ( - {model.errors.join('\n')} + {showValidation && model.isInvalid && model.error && ( + {model.error} )} {models.length - 1 !== arrayIndex && } diff --git a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/number_row.test.tsx b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/number_row.test.tsx new file mode 100644 index 0000000000000..2173d92819e3d --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/number_row.test.tsx @@ -0,0 +1,69 @@ +/* + * 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 { shallow } from 'enzyme'; + +import { NumberRow, NumberRowProps } from './number_row'; + +describe('NumberRow', () => { + let defaultProps: NumberRowProps; + + beforeEach(() => { + defaultProps = { + autoFocus: false, + disableDelete: false, + isInvalid: false, + labelledbyId: 'numberList', + model: { value: 1, id: '1', isInvalid: false }, + range: { + min: 1, + max: 10, + minInclusive: true, + maxInclusive: true, + within: jest.fn(() => true), + }, + onChange: jest.fn(), + onBlur: jest.fn(), + onDelete: jest.fn(), + }; + }); + + test('should be rendered with default set of props', () => { + const comp = shallow(); + + expect(comp).toMatchSnapshot(); + }); + + test('should call onDelete', () => { + const comp = shallow(); + comp.find('EuiButtonIcon').simulate('click'); + + expect(defaultProps.onDelete).lastCalledWith(defaultProps.model.id); + }); + + test('should call onChange', () => { + const comp = shallow(); + comp.find('EuiFieldNumber').prop('onChange')!({ target: { value: '5' } } as React.ChangeEvent< + HTMLInputElement + >); + + expect(defaultProps.onChange).lastCalledWith({ id: defaultProps.model.id, value: '5' }); + }); +}); diff --git a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/number_row.tsx b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/number_row.tsx index cef574e806e01..23e671180e980 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/number_row.tsx +++ b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/number_row.tsx @@ -17,13 +17,13 @@ * under the License. */ -import React from 'react'; +import React, { useCallback } from 'react'; import { EuiFieldNumber, EuiFlexGroup, EuiFlexItem, EuiButtonIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { Range } from '../../../../../../utils/range'; -interface NumberRowProps { +export interface NumberRowProps { autoFocus: boolean; disableDelete: boolean; isInvalid: boolean; @@ -31,7 +31,6 @@ interface NumberRowProps { model: NumberRowModel; range: Range; onBlur(): void; - onFocus?(): void; onChange({ id, value }: { id: string; value: string }): void; onDelete(index: string): void; } @@ -40,7 +39,7 @@ export interface NumberRowModel { id: string; isInvalid: boolean; value: number | ''; - errors?: string[]; + error?: string; } function NumberRow({ @@ -52,7 +51,6 @@ function NumberRow({ range, onBlur, onDelete, - onFocus, onChange, }: NumberRowProps) { const deleteBtnAriaLabel = i18n.translate( @@ -63,11 +61,16 @@ function NumberRow({ } ); - const onValueChanged = (event: React.ChangeEvent) => - onChange({ - value: event.target.value, - id: model.id, - }); + const onValueChanged = useCallback( + (event: React.ChangeEvent) => + onChange({ + value: event.target.value, + id: model.id, + }), + [onChange, model.id] + ); + + const onDeleteFn = useCallback(() => onDelete(model.id), [onDelete, model.id]); return ( @@ -81,7 +84,6 @@ function NumberRow({ defaultMessage: 'Enter a value', })} onChange={onValueChanged} - onFocus={onFocus} value={model.value} fullWidth={true} min={range.min} @@ -95,7 +97,7 @@ function NumberRow({ title={deleteBtnAriaLabel} color="danger" iconType="trash" - onClick={() => onDelete(model.id)} + onClick={onDeleteFn} disabled={disableDelete} /> diff --git a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/utils.test.ts b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/utils.test.ts new file mode 100644 index 0000000000000..3928c0a62eeaa --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/utils.test.ts @@ -0,0 +1,218 @@ +/* + * 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 { + getInitModelList, + getUpdatedModels, + validateOrder, + hasInvalidValues, + parse, + validateValue, + getNextModel, + getRange, +} from './utils'; +import { Range } from '../../../../../../utils/range'; +import { NumberRowModel } from './number_row'; + +describe('NumberList utils', () => { + let modelList: NumberRowModel[]; + let range: Range; + + beforeEach(() => { + modelList = [{ value: 1, id: '1', isInvalid: false }, { value: 2, id: '2', isInvalid: false }]; + range = { + min: 1, + max: 10, + minInclusive: true, + maxInclusive: true, + within: jest.fn(() => true), + }; + }); + + describe('getInitModelList', () => { + test('should return list with default model when number list is empty', () => { + const models = getInitModelList([]); + + expect(models).toEqual([{ value: 0, id: expect.any(String), isInvalid: false }]); + }); + + test('should return model list', () => { + const models = getInitModelList([1, undefined]); + + expect(models).toEqual([ + { value: 1, id: expect.any(String), isInvalid: false }, + { value: '', id: expect.any(String), isInvalid: false }, + ]); + }); + }); + + describe('getUpdatedModels', () => { + test('should return model list when number list is empty', () => { + const updatedModelList = getUpdatedModels([], modelList, range); + + expect(updatedModelList).toEqual([{ value: 0, id: expect.any(String), isInvalid: false }]); + }); + + test('should not update model list when number list is the same', () => { + const updatedModelList = getUpdatedModels([1, 2], modelList, range); + + expect(updatedModelList).toEqual(modelList); + }); + + test('should update model list when number list was changed', () => { + const updatedModelList = getUpdatedModels([1, 3], modelList, range); + modelList[1].value = 3; + expect(updatedModelList).toEqual(modelList); + }); + + test('should update model list when number list increased', () => { + const updatedModelList = getUpdatedModels([1, 2, 3], modelList, range); + expect(updatedModelList).toEqual([ + ...modelList, + { value: 3, id: expect.any(String), isInvalid: false }, + ]); + }); + + test('should update model list when number list decreased', () => { + const updatedModelList = getUpdatedModels([2], modelList, range); + expect(updatedModelList).toEqual([{ value: 2, id: '1', isInvalid: false }]); + }); + + test('should update model list when number list has undefined value', () => { + const updatedModelList = getUpdatedModels([1, undefined], modelList, range); + modelList[1].value = ''; + modelList[1].isInvalid = true; + expect(updatedModelList).toEqual(modelList); + }); + + test('should update model list when number order is invalid', () => { + const updatedModelList = getUpdatedModels([1, 3, 2], modelList, range, 2); + expect(updatedModelList).toEqual([ + modelList[0], + { ...modelList[1], value: 3 }, + { value: 2, id: expect.any(String), isInvalid: true }, + ]); + }); + }); + + describe('validateOrder', () => { + test('should return true when order is valid', () => { + expect(validateOrder([1, 2])).toEqual({ + isValidOrder: true, + }); + }); + + test('should return true when a number is undefined', () => { + expect(validateOrder([1, undefined])).toEqual({ + isValidOrder: true, + }); + }); + + test('should return false when order is invalid', () => { + expect(validateOrder([2, 1])).toEqual({ + isValidOrder: false, + modelIndex: 1, + }); + }); + }); + + describe('hasInvalidValues', () => { + test('should return false when there are no invalid models', () => { + expect(hasInvalidValues(modelList)).toBeFalsy(); + }); + + test('should return true when there is an invalid model', () => { + modelList[1].isInvalid = true; + expect(hasInvalidValues(modelList)).toBeTruthy(); + }); + }); + + describe('parse', () => { + test('should return a number', () => { + expect(parse('3')).toBe(3); + }); + + test('should return an empty string when value is invalid', () => { + expect(parse('')).toBe(''); + expect(parse('test')).toBe(''); + expect(parse('NaN')).toBe(''); + }); + }); + + describe('validateValue', () => { + test('should return valid', () => { + expect(validateValue(3, range)).toEqual({ isInvalid: false }); + }); + + test('should return invalid', () => { + range.within = jest.fn(() => false); + expect(validateValue(11, range)).toEqual({ isInvalid: true, error: expect.any(String) }); + }); + }); + + describe('getNextModel', () => { + test('should return 3 as next value', () => { + expect(getNextModel(modelList, range)).toEqual({ + value: 3, + id: expect.any(String), + isInvalid: false, + }); + }); + + test('should return 1 as next value', () => { + expect(getNextModel([{ value: '', id: '2', isInvalid: false }], range)).toEqual({ + value: 1, + id: expect.any(String), + isInvalid: false, + }); + }); + + test('should return 9 as next value', () => { + expect(getNextModel([{ value: 11, id: '2', isInvalid: false }], range)).toEqual({ + value: 9, + id: expect.any(String), + isInvalid: false, + }); + }); + }); + + describe('getRange', () => { + test('should return default range', () => { + expect(getRange()).toEqual({ + min: 0, + max: Infinity, + maxInclusive: false, + minInclusive: true, + }); + }); + + test('should return parsed range', () => { + expect(getRange('(-Infinity, 100]')).toEqual({ + min: -Infinity, + max: 100, + maxInclusive: true, + minInclusive: false, + }); + }); + + test('should throw an error', () => { + expect(() => getRange('test')).toThrowError(); + }); + }); +}); diff --git a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/utils.ts b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/utils.ts index f90f545aeffba..563e8f0a6a9b7 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/components/number_list/utils.ts +++ b/src/legacy/ui/public/vis/editors/default/controls/components/number_list/utils.ts @@ -27,6 +27,7 @@ import { NumberRowModel } from './number_row'; const EMPTY_STRING = ''; const defaultRange = parseRange('[0,Infinity)'); const generateId = htmlIdGenerator(); +const defaultModel = { value: 0, id: generateId(), isInvalid: false }; function parse(value: string) { const parsedValue = parseFloat(value); @@ -42,44 +43,37 @@ function getRange(range?: string): Range { } function validateValue(value: number | '', numberRange: Range) { - const result = { - isValid: true, - errors: [] as string[], + const result: { isInvalid: boolean; error?: string } = { + isInvalid: false, }; if (value === EMPTY_STRING) { - result.isValid = false; + result.isInvalid = true; } else if (!numberRange.within(value)) { - result.isValid = false; - result.errors.push( - i18n.translate('common.ui.aggTypes.numberList.invalidRangeErrorMessage', { - defaultMessage: 'The value should be in the range of {min} to {max}.', - values: { min: numberRange.min, max: numberRange.max }, - }) - ); + result.isInvalid = true; + result.error = i18n.translate('common.ui.aggTypes.numberList.invalidRangeErrorMessage', { + defaultMessage: 'The value should be in the range of {min} to {max}.', + values: { min: numberRange.min, max: numberRange.max }, + }); } return result; } -function validateOrder(list: NumberRowModel[]) { - let isInvalidOrder = false; - list.forEach((model, index, array) => { - const previousModel = array[index - 1]; - if (previousModel && model.value !== EMPTY_STRING) { - const isInvalidOrderOfItem = model.value <= previousModel.value; - - if (!model.isInvalid && isInvalidOrderOfItem) { - model.isInvalid = true; - } +function validateOrder(list: Array) { + const result: { isValidOrder: boolean; modelIndex?: number } = { + isValidOrder: true, + }; - if (isInvalidOrderOfItem) { - isInvalidOrder = true; - } + list.forEach((inputValue, index, array) => { + const previousModel = array[index - 1]; + if (previousModel !== undefined && inputValue !== undefined && inputValue <= previousModel) { + result.isValidOrder = false; + result.modelIndex = index; } }); - return isInvalidOrder; + return result; } function getNextModel(list: NumberRowModel[], range: Range): NumberRowModel { @@ -104,26 +98,27 @@ function getInitModelList(list: Array): NumberRowModel[] { id: generateId(), isInvalid: false, })) - : [{ value: 0, id: generateId(), isInvalid: false }]; + : [defaultModel]; } function getUpdatedModels( numberList: Array, modelList: NumberRowModel[], - numberRange: Range + numberRange: Range, + invalidOrderModelIndex?: number ): NumberRowModel[] { if (!numberList.length) { - return modelList; + return [defaultModel]; } return numberList.map((number, index) => { const model = modelList[index] || { id: generateId() }; const newValue: NumberRowModel['value'] = number === undefined ? EMPTY_STRING : number; - const { isValid, errors } = validateValue(newValue, numberRange); + const { isInvalid, error } = validateValue(newValue, numberRange); return { ...model, value: newValue, - isInvalid: !isValid, - errors, + isInvalid: invalidOrderModelIndex === index ? true : isInvalid, + error, }; }); } diff --git a/src/legacy/ui/public/vis/editors/default/controls/percentile_ranks.tsx b/src/legacy/ui/public/vis/editors/default/controls/percentile_ranks.tsx index 8b63bb23475b2..420bf8a87c3d5 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/percentile_ranks.tsx +++ b/src/legacy/ui/public/vis/editors/default/controls/percentile_ranks.tsx @@ -37,7 +37,7 @@ function PercentileRanksEditor({ }); const [isValid, setIsValid] = useState(true); - const setModelValidy = (isListValid: boolean) => { + const setModelValidity = (isListValid: boolean) => { setIsValid(isListValid); setValidity(isListValid); }; @@ -62,7 +62,7 @@ function PercentileRanksEditor({ showValidation={showValidation} onChange={setValue} setTouched={setTouched} - setValidity={setModelValidy} + setValidity={setModelValidity} /> ); diff --git a/src/legacy/ui/public/vis/editors/default/controls/percentiles.tsx b/src/legacy/ui/public/vis/editors/default/controls/percentiles.tsx index 158ae3442ff0c..b8ad212edead9 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/percentiles.tsx +++ b/src/legacy/ui/public/vis/editors/default/controls/percentiles.tsx @@ -37,7 +37,7 @@ function PercentilesEditor({ }); const [isValid, setIsValid] = useState(true); - const setModelValidy = (isListValid: boolean) => { + const setModelValidity = (isListValid: boolean) => { setIsValid(isListValid); setValidity(isListValid); }; @@ -62,7 +62,7 @@ function PercentilesEditor({ showValidation={showValidation} onChange={setValue} setTouched={setTouched} - setValidity={setModelValidy} + setValidity={setModelValidity} /> ); diff --git a/src/legacy/ui/public/vis/editors/default/controls/sub_metric.tsx b/src/legacy/ui/public/vis/editors/default/controls/sub_metric.tsx index 836049eb95cab..df1640273135e 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/sub_metric.tsx +++ b/src/legacy/ui/public/vis/editors/default/controls/sub_metric.tsx @@ -21,7 +21,7 @@ import React, { useEffect, useState } from 'react'; import { EuiFormLabel, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { AggParamType } from '../../../../agg_types/param_types/agg'; -import { AggConfig } from '../../..'; +import { AggConfig } from '../../../../agg_types/agg_config'; import { AggParamEditorProps, DefaultEditorAggParams, AggGroupNames } from '..'; function SubMetricParamEditor({ diff --git a/src/legacy/ui/public/vislib/_index.scss b/src/legacy/ui/public/vislib/_index.scss index 4e4ba8175444d..0344fbb5359ec 100644 --- a/src/legacy/ui/public/vislib/_index.scss +++ b/src/legacy/ui/public/vislib/_index.scss @@ -3,3 +3,4 @@ @import './lib/index'; @import './visualizations/point_series/index'; +@import './visualizations/gauges/index'; diff --git a/src/legacy/ui/public/vislib/visualizations/gauges/_index.scss b/src/legacy/ui/public/vislib/visualizations/gauges/_index.scss new file mode 100644 index 0000000000000..1c6b5e669a94b --- /dev/null +++ b/src/legacy/ui/public/vislib/visualizations/gauges/_index.scss @@ -0,0 +1 @@ +@import './meter'; diff --git a/src/legacy/ui/public/vislib/visualizations/gauges/_meter.scss b/src/legacy/ui/public/vislib/visualizations/gauges/_meter.scss new file mode 100644 index 0000000000000..971eaa0eab2f6 --- /dev/null +++ b/src/legacy/ui/public/vislib/visualizations/gauges/_meter.scss @@ -0,0 +1,3 @@ +.visGauge__meter--outline { + stroke: $euiBorderColor; +} diff --git a/src/legacy/ui/public/vislib/visualizations/gauges/meter.js b/src/legacy/ui/public/vislib/visualizations/gauges/meter.js index b6a308271497a..7533ae01bc4ac 100644 --- a/src/legacy/ui/public/vislib/visualizations/gauges/meter.js +++ b/src/legacy/ui/public/vislib/visualizations/gauges/meter.js @@ -38,6 +38,7 @@ const defaultConfig = { percentageMode: true, innerSpace: 5, extents: [0, 10000], + outline: false, scale: { show: true, color: '#666', @@ -248,11 +249,13 @@ export class MeterGauge { gauges .append('path') .attr('d', bgArc) + .attr('class', this.gaugeConfig.outline ? 'visGauge__meter--outline' : undefined) .style('fill', this.gaugeConfig.style.bgFill); const series = gauges .append('path') .attr('d', arc) + .attr('class', this.gaugeConfig.outline ? 'visGauge__meter--outline' : undefined) .style('fill', (d) => this.getColorBucket(Math.max(min, d.y))); const smallContainer = svg.node().getBBox().height < 70; diff --git a/src/legacy/ui/ui_render/ui_render_mixin.js b/src/legacy/ui/ui_render/ui_render_mixin.js index 883358e0d3c9a..0d05ea259d1a1 100644 --- a/src/legacy/ui/ui_render/ui_render_mixin.js +++ b/src/legacy/ui/ui_render/ui_render_mixin.js @@ -185,7 +185,7 @@ export function uiRenderMixin(kbnServer, server, config) { async function getUiSettings({ request, includeUserProvidedConfig }) { const uiSettings = request.getUiSettingsService(); return props({ - defaults: uiSettings.getDefaults(), + defaults: uiSettings.getRegistered(), user: includeUserProvidedConfig && uiSettings.getUserProvided() }); } 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 9f553b37935d7..dd3f12903abca 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 @@ -20,7 +20,7 @@ import sinon from 'sinon'; import expect from '@kbn/expect'; -import { SavedObjectsClientMock } from '../../../../core/server/mocks'; +import { savedObjectsClientMock } from '../../../../core/server/mocks'; import * as uiSettingsServiceFactoryNS from '../ui_settings_service_factory'; import * as getUiSettingsServiceForRequestNS from '../ui_settings_service_for_request'; // @ts-ignore @@ -73,7 +73,7 @@ describe('uiSettingsMixin()', () => { newPlatform: { __internals: { uiSettings: { - setDefaults: sinon.stub(), + register: sinon.stub(), }, }, }, @@ -93,9 +93,9 @@ describe('uiSettingsMixin()', () => { it('passes uiSettingsDefaults to the new platform', () => { const { kbnServer } = setup(); - sinon.assert.calledOnce(kbnServer.newPlatform.__internals.uiSettings.setDefaults); + sinon.assert.calledOnce(kbnServer.newPlatform.__internals.uiSettings.register); sinon.assert.calledWithExactly( - kbnServer.newPlatform.__internals.uiSettings.setDefaults, + kbnServer.newPlatform.__internals.uiSettings.register, uiSettingDefaults ); }); @@ -129,7 +129,7 @@ describe('uiSettingsMixin()', () => { sinon.assert.notCalled(uiSettingsServiceFactoryStub); - const savedObjectsClient = SavedObjectsClientMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); decorations.server.uiSettingsServiceFactory({ savedObjectsClient, }); diff --git a/src/legacy/ui/ui_settings/ui_settings_mixin.js b/src/legacy/ui/ui_settings/ui_settings_mixin.js index 8c7ef25c6f8d7..64251d290776c 100644 --- a/src/legacy/ui/ui_settings/ui_settings_mixin.js +++ b/src/legacy/ui/ui_settings/ui_settings_mixin.js @@ -19,12 +19,6 @@ import { uiSettingsServiceFactory } from './ui_settings_service_factory'; import { getUiSettingsServiceForRequest } from './ui_settings_service_for_request'; -import { - deleteRoute, - getRoute, - setManyRoute, - setRoute, -} from './routes'; export function uiSettingsMixin(kbnServer, server) { const { uiSettingDefaults = {} } = kbnServer.uiExports; @@ -43,7 +37,7 @@ export function uiSettingsMixin(kbnServer, server) { return acc; }, {}); - kbnServer.newPlatform.__internals.uiSettings.setDefaults(mergedUiSettingDefaults); + kbnServer.newPlatform.__internals.uiSettings.register(mergedUiSettingDefaults); server.decorate('server', 'uiSettingsServiceFactory', (options = {}) => { return uiSettingsServiceFactory(server, options); @@ -58,9 +52,4 @@ export function uiSettingsMixin(kbnServer, server) { server.uiSettings has been removed, see https://github.com/elastic/kibana/pull/12243. `); }); - - server.route(deleteRoute); - server.route(getRoute); - server.route(setManyRoute); - server.route(setRoute); } diff --git a/src/optimize/dynamic_dll_plugin/dll_compiler.js b/src/optimize/dynamic_dll_plugin/dll_compiler.js index 5b9dd2d04a550..3f3bb3e4e196c 100644 --- a/src/optimize/dynamic_dll_plugin/dll_compiler.js +++ b/src/optimize/dynamic_dll_plugin/dll_compiler.js @@ -25,13 +25,12 @@ import fs from 'fs'; import webpack from 'webpack'; import { promisify } from 'util'; import path from 'path'; -import rimraf from 'rimraf'; +import del from 'del'; const readFileAsync = promisify(fs.readFile); const mkdirAsync = promisify(fs.mkdir); const existsAsync = promisify(fs.exists); const writeFileAsync = promisify(fs.writeFile); -const rimrafAsync = promisify(rimraf); export class DllCompiler { static getRawDllConfig(uiBundles = {}, babelLoaderCacheDir = '', threadLoaderPoolConfig = {}) { @@ -267,7 +266,7 @@ export class DllCompiler { // Delete the built dll, as it contains invalid modules, and reject listing // all the not allowed modules try { - await rimrafAsync(this.rawDllConfig.outputPath); + await del(this.rawDllConfig.outputPath); } catch (e) { return reject(e); } diff --git a/src/plugins/data/common/field_formats/converters/url.ts b/src/plugins/data/common/field_formats/converters/url.ts index 984cced336d2b..6c00f11a408dc 100644 --- a/src/plugins/data/common/field_formats/converters/url.ts +++ b/src/plugins/data/common/field_formats/converters/url.ts @@ -198,5 +198,3 @@ export class UrlFormat extends FieldFormat { } }; } - -// console.log(UrlFormat); 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 dd55cd1eacdc2..fd8f286a9d8f6 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 @@ -27,7 +27,8 @@ import { import { HelloWorldContainer } from '../../../../test_samples/embeddables/hello_world_container'; import { ContactCardEmbeddable } from '../../../../test_samples/embeddables/contact_card/contact_card_embeddable'; import { ContainerInput } from '../../../../containers'; -import { mount } from 'enzyme'; +import { mountWithIntl as mount } from 'test_utils/enzyme_helpers'; +import { ReactWrapper } from 'enzyme'; // @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; @@ -55,7 +56,7 @@ test('createNewEmbeddable() add embeddable to container', async () => { }; const container = new HelloWorldContainer(input, { getEmbeddableFactory } as any); const onClose = jest.fn(); - const component = mount( + const component = mount( { notifications={core.notifications} SavedObjectFinder={() => null} /> - ); + ) as ReactWrapper; expect(Object.values(container.getInput().panels).length).toBe(0); component.instance().createNewEmbeddable(CONTACT_CARD_EMBEDDABLE); @@ -109,7 +110,7 @@ test('selecting embeddable in "Create new ..." list calls createNewEmbeddable()' notifications={core.notifications} SavedObjectFinder={() => null} /> - ); + ) as ReactWrapper; const spy = jest.fn(); component.instance().createNewEmbeddable = spy; 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 8efc9e5b996c5..4f2ae7ab19bcb 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 @@ -22,21 +22,20 @@ import React from 'react'; import { CoreSetup } from 'src/core/public'; import { - EuiFlexGroup, - EuiFlexItem, + EuiButton, + EuiContextMenuItem, + EuiContextMenuPanel, EuiFlyout, EuiFlyoutBody, EuiFlyoutFooter, EuiFlyoutHeader, - // @ts-ignore - EuiSuperSelect, + EuiPopover, EuiTitle, - EuiText, } from '@elastic/eui'; import { IContainer } from '../../../../containers'; import { EmbeddableFactoryNotFoundError } from '../../../../errors'; -import { GetEmbeddableFactory, GetEmbeddableFactories } from '../../../../types'; +import { GetEmbeddableFactories, GetEmbeddableFactory } from '../../../../types'; interface Props { onClose: () => void; @@ -47,9 +46,21 @@ interface Props { SavedObjectFinder: React.ComponentType; } -export class AddPanelFlyout extends React.Component { +interface State { + isCreateMenuOpen: boolean; +} + +function capitalize([first, ...letters]: string) { + return `${first.toUpperCase()}${letters.join('')}`; +} + +export class AddPanelFlyout extends React.Component { private lastToast: any; + public state = { + isCreateMenuOpen: false, + }; + constructor(props: Props) { super(props); } @@ -96,41 +107,27 @@ export class AddPanelFlyout extends React.Component { this.showToast(name); }; - private getSelectCreateNewOptions() { - const list = [ - { - value: 'createNew', - inputDisplay: ( - - - - ), - }, - ...[...this.props.getAllFactories()] - .filter( - factory => factory.isEditable() && !factory.isContainerType && factory.canCreateNew() - ) - .map(factory => ({ - inputDisplay: ( - - - - ), - value: factory.type, - 'data-test-subj': `createNew-${factory.type}`, - })), - ]; - - return list; + private toggleCreateMenu = () => { + this.setState(prevState => ({ isCreateMenuOpen: !prevState.isCreateMenuOpen })); + }; + + private closeCreateMenu = () => { + this.setState({ isCreateMenuOpen: false }); + }; + + private getCreateMenuItems() { + return [...this.props.getAllFactories()] + .filter(factory => factory.isEditable() && !factory.isContainerType && factory.canCreateNew()) + .map(factory => ( + this.createNewEmbeddable(factory.type)} + className="embPanel__addItem" + > + {capitalize(factory.getDisplayName())} + + )); } public render() { @@ -150,6 +147,7 @@ export class AddPanelFlyout extends React.Component { })} /> ); + return ( @@ -161,16 +159,28 @@ export class AddPanelFlyout extends React.Component { {savedObjectsFinder} - - - this.createNewEmbeddable(value)} - /> - - + iconType="arrowDown" + iconSide="right" + onClick={this.toggleCreateMenu} + > + + + } + isOpen={this.state.isCreateMenuOpen} + closePopover={this.closeCreateMenu} + panelPaddingSize="none" + anchorPosition="upLeft" + > + + ); diff --git a/src/plugins/feature_catalogue/README.md b/src/plugins/feature_catalogue/README.md new file mode 100644 index 0000000000000..68584e7ed2ce1 --- /dev/null +++ b/src/plugins/feature_catalogue/README.md @@ -0,0 +1,26 @@ +# Feature catalogue plugin + +Replaces the legacy `ui/registry/feature_catalogue` module for registering "features" that should be showed in the home +page's feature catalogue. This should not be confused with the "feature" plugin for registering features used to derive +UI capabilities for feature controls. + +## Example registration + +```ts +// For legacy plugins +import { npSetup } from 'ui/new_platform'; +npSetup.plugins.feature_catalogue.register(/* same details here */); + +// For new plugins: first add 'feature_catalogue` to the list of `optionalPlugins` +// in your kibana.json file. Then access the plugin directly in `setup`: + +class MyPlugin { + setup(core, plugins) { + if (plugins.feature_catalogue) { + plugins.feature_catalogue.register(/* same details here. */); + } + } +} +``` + +Note that the old module supported providing a Angular DI function to receive Angular dependencies. This is no longer supported as we migrate away from Angular and will be removed in 8.0. diff --git a/src/plugins/feature_catalogue/kibana.json b/src/plugins/feature_catalogue/kibana.json new file mode 100644 index 0000000000000..3f39c9361f047 --- /dev/null +++ b/src/plugins/feature_catalogue/kibana.json @@ -0,0 +1,6 @@ +{ + "id": "feature_catalogue", + "version": "kibana", + "server": false, + "ui": true +} diff --git a/src/legacy/ui/ui_settings/routes/index.ts b/src/plugins/feature_catalogue/public/index.ts similarity index 75% rename from src/legacy/ui/ui_settings/routes/index.ts rename to src/plugins/feature_catalogue/public/index.ts index f3c9d4f0d8d14..dd241a317c4a6 100644 --- a/src/legacy/ui/ui_settings/routes/index.ts +++ b/src/plugins/feature_catalogue/public/index.ts @@ -17,7 +17,8 @@ * under the License. */ -export { deleteRoute } from './delete'; -export { getRoute } from './get'; -export { setManyRoute } from './set_many'; -export { setRoute } from './set'; +export { FeatureCatalogueSetup, FeatureCatalogueStart } from './plugin'; +export { FeatureCatalogueEntry, FeatureCatalogueCategory } from './services'; +import { FeatureCataloguePlugin } from './plugin'; + +export const plugin = () => new FeatureCataloguePlugin(); diff --git a/src/plugins/feature_catalogue/public/plugin.test.mocks.ts b/src/plugins/feature_catalogue/public/plugin.test.mocks.ts new file mode 100644 index 0000000000000..c0da6a179204b --- /dev/null +++ b/src/plugins/feature_catalogue/public/plugin.test.mocks.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. + */ + +import { featureCatalogueRegistryMock } from './services/feature_catalogue_registry.mock'; + +export const registryMock = featureCatalogueRegistryMock.create(); +jest.doMock('./services', () => ({ + FeatureCatalogueRegistry: jest.fn(() => registryMock), +})); diff --git a/src/plugins/feature_catalogue/public/plugin.test.ts b/src/plugins/feature_catalogue/public/plugin.test.ts new file mode 100644 index 0000000000000..8bbbb973b459e --- /dev/null +++ b/src/plugins/feature_catalogue/public/plugin.test.ts @@ -0,0 +1,49 @@ +/* + * 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 { registryMock } from './plugin.test.mocks'; +import { FeatureCataloguePlugin } from './plugin'; + +describe('FeatureCataloguePlugin', () => { + beforeEach(() => { + registryMock.setup.mockClear(); + registryMock.start.mockClear(); + }); + + describe('setup', () => { + test('wires up and returns registry', async () => { + const setup = await new FeatureCataloguePlugin().setup(); + expect(registryMock.setup).toHaveBeenCalledWith(); + expect(setup.register).toBeDefined(); + }); + }); + + describe('start', () => { + test('wires up and returns registry', async () => { + const service = new FeatureCataloguePlugin(); + await service.setup(); + const core = { application: { capabilities: { catalogue: {} } } } as any; + const start = await service.start(core); + expect(registryMock.start).toHaveBeenCalledWith({ + capabilities: core.application.capabilities, + }); + expect(start.get).toBeDefined(); + }); + }); +}); diff --git a/src/legacy/ui/ui_settings/routes/set.ts b/src/plugins/feature_catalogue/public/plugin.ts similarity index 50% rename from src/legacy/ui/ui_settings/routes/set.ts rename to src/plugins/feature_catalogue/public/plugin.ts index 1f1ab17a0daf7..46a70baff488a 100644 --- a/src/legacy/ui/ui_settings/routes/set.ts +++ b/src/plugins/feature_catalogue/public/plugin.ts @@ -16,40 +16,35 @@ * specific language governing permissions and limitations * under the License. */ -import { Legacy } from 'kibana'; -import Joi from 'joi'; -async function handleRequest(request: Legacy.Request) { - const { key } = request.params; - const { value } = request.payload as any; - const uiSettings = request.getUiSettingsService(); +import { CoreStart, Plugin } from 'src/core/public'; +import { + FeatureCatalogueRegistry, + FeatureCatalogueRegistrySetup, + FeatureCatalogueRegistryStart, +} from './services'; - await uiSettings.set(key, value); +export class FeatureCataloguePlugin + implements Plugin { + private readonly featuresCatalogueRegistry = new FeatureCatalogueRegistry(); - return { - settings: await uiSettings.getUserProvided(), - }; + public async setup() { + return { + ...this.featuresCatalogueRegistry.setup(), + }; + } + + public async start(core: CoreStart) { + return { + ...this.featuresCatalogueRegistry.start({ + capabilities: core.application.capabilities, + }), + }; + } } -export const setRoute = { - path: '/api/kibana/settings/{key}', - method: 'POST', - config: { - validate: { - params: Joi.object() - .keys({ - key: Joi.string().required(), - }) - .default(), +/** @public */ +export type FeatureCatalogueSetup = FeatureCatalogueRegistrySetup; - payload: Joi.object() - .keys({ - value: Joi.any().required(), - }) - .required(), - }, - handler(request: Legacy.Request) { - return handleRequest(request); - }, - }, -}; +/** @public */ +export type FeatureCatalogueStart = FeatureCatalogueRegistryStart; diff --git a/src/plugins/feature_catalogue/public/services/feature_catalogue_registry.mock.ts b/src/plugins/feature_catalogue/public/services/feature_catalogue_registry.mock.ts new file mode 100644 index 0000000000000..54bdd42c1cca9 --- /dev/null +++ b/src/plugins/feature_catalogue/public/services/feature_catalogue_registry.mock.ts @@ -0,0 +1,54 @@ +/* + * 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 { + FeatureCatalogueRegistrySetup, + FeatureCatalogueRegistryStart, + FeatureCatalogueRegistry, +} from './feature_catalogue_registry'; + +const createSetupMock = (): jest.Mocked => { + const setup = { + register: jest.fn(), + }; + return setup; +}; + +const createStartMock = (): jest.Mocked => { + const start = { + get: jest.fn(), + }; + return start; +}; + +const createMock = (): jest.Mocked> => { + const service = { + setup: jest.fn(), + start: jest.fn(), + }; + service.setup.mockImplementation(createSetupMock); + service.start.mockImplementation(createStartMock); + return service; +}; + +export const featureCatalogueRegistryMock = { + createSetup: createSetupMock, + createStart: createStartMock, + create: createMock, +}; diff --git a/src/plugins/feature_catalogue/public/services/feature_catalogue_registry.test.ts b/src/plugins/feature_catalogue/public/services/feature_catalogue_registry.test.ts new file mode 100644 index 0000000000000..b174a68aa53be --- /dev/null +++ b/src/plugins/feature_catalogue/public/services/feature_catalogue_registry.test.ts @@ -0,0 +1,87 @@ +/* + * 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 { + FeatureCatalogueRegistry, + FeatureCatalogueCategory, + FeatureCatalogueEntry, +} from './feature_catalogue_registry'; + +const DASHBOARD_FEATURE: FeatureCatalogueEntry = { + id: 'dashboard', + title: 'Dashboard', + description: 'Display and share a collection of visualizations and saved searches.', + icon: 'dashboardApp', + path: `/app/kibana#dashboard`, + showOnHomePage: true, + category: FeatureCatalogueCategory.DATA, +}; + +describe('FeatureCatalogueRegistry', () => { + describe('setup', () => { + test('throws when registering duplicate id', () => { + const setup = new FeatureCatalogueRegistry().setup(); + setup.register(DASHBOARD_FEATURE); + expect(() => setup.register(DASHBOARD_FEATURE)).toThrowErrorMatchingInlineSnapshot( + `"Feature with id [dashboard] has already been registered. Use a unique id."` + ); + }); + }); + + describe('start', () => { + describe('capabilities filtering', () => { + test('retains items with no entry in capabilities', () => { + const service = new FeatureCatalogueRegistry(); + service.setup().register(DASHBOARD_FEATURE); + const capabilities = { catalogue: {} } as any; + expect(service.start({ capabilities }).get()).toEqual([DASHBOARD_FEATURE]); + }); + + test('retains items with true in capabilities', () => { + const service = new FeatureCatalogueRegistry(); + service.setup().register(DASHBOARD_FEATURE); + const capabilities = { catalogue: { dashboard: true } } as any; + expect(service.start({ capabilities }).get()).toEqual([DASHBOARD_FEATURE]); + }); + + test('removes items with false in capabilities', () => { + const service = new FeatureCatalogueRegistry(); + service.setup().register(DASHBOARD_FEATURE); + const capabilities = { catalogue: { dashboard: false } } as any; + expect(service.start({ capabilities }).get()).toEqual([]); + }); + }); + }); + + describe('title sorting', () => { + test('sorts by title ascending', () => { + const service = new FeatureCatalogueRegistry(); + const setup = service.setup(); + setup.register({ id: '1', title: 'Orange' } as any); + setup.register({ id: '2', title: 'Apple' } as any); + setup.register({ id: '3', title: 'Banana' } as any); + const capabilities = { catalogue: {} } as any; + expect(service.start({ capabilities }).get()).toEqual([ + { id: '2', title: 'Apple' }, + { id: '3', title: 'Banana' }, + { id: '1', title: 'Orange' }, + ]); + }); + }); +}); diff --git a/src/plugins/feature_catalogue/public/services/feature_catalogue_registry.ts b/src/plugins/feature_catalogue/public/services/feature_catalogue_registry.ts new file mode 100644 index 0000000000000..6ab342f37dfd9 --- /dev/null +++ b/src/plugins/feature_catalogue/public/services/feature_catalogue_registry.ts @@ -0,0 +1,86 @@ +/* + * 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 { Capabilities } from 'src/core/public'; +import { IconType } from '@elastic/eui'; + +/** @public */ +export enum FeatureCatalogueCategory { + ADMIN = 'admin', + DATA = 'data', + OTHER = 'other', +} + +/** @public */ +export interface FeatureCatalogueEntry { + /** Unique string identifier for this feature. */ + readonly id: string; + /** Title of feature displayed to the user. */ + readonly title: string; + /** {@link FeatureCatalogueCategory} to display this feature in. */ + readonly category: FeatureCatalogueCategory; + /** One-line description of feature displayed to the user. */ + readonly description: string; + /** EUI `IconType` for icon to be displayed to the user. EUI supports any known EUI icon, SVG URL, or ReactElement. */ + readonly icon: IconType; + /** URL path to link to this future. Should not include the basePath. */ + readonly path: string; + /** Whether or not this link should be shown on the front page of Kibana. */ + readonly showOnHomePage: boolean; +} + +export class FeatureCatalogueRegistry { + private readonly features = new Map(); + + public setup() { + return { + register: (feature: FeatureCatalogueEntry) => { + if (this.features.has(feature.id)) { + throw new Error( + `Feature with id [${feature.id}] has already been registered. Use a unique id.` + ); + } + + this.features.set(feature.id, feature); + }, + }; + } + + public start({ capabilities }: { capabilities: Capabilities }) { + return { + get: (): readonly FeatureCatalogueEntry[] => + [...this.features.values()] + .filter(entry => capabilities.catalogue[entry.id] !== false) + .sort(compareByKey('title')), + }; + } +} + +export type FeatureCatalogueRegistrySetup = ReturnType; +export type FeatureCatalogueRegistryStart = ReturnType; + +const compareByKey = (key: keyof T) => (left: T, right: T) => { + if (left[key] < right[key]) { + return -1; + } else if (left[key] > right[key]) { + return 1; + } else { + return 0; + } +}; diff --git a/src/plugins/feature_catalogue/public/services/index.ts b/src/plugins/feature_catalogue/public/services/index.ts new file mode 100644 index 0000000000000..17433264f5a42 --- /dev/null +++ b/src/plugins/feature_catalogue/public/services/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 * from './feature_catalogue_registry'; diff --git a/src/legacy/ui/ui_settings/routes/get.ts b/src/plugins/kibana_utils/public/core/create_getter_setter.ts similarity index 68% rename from src/legacy/ui/ui_settings/routes/get.ts rename to src/plugins/kibana_utils/public/core/create_getter_setter.ts index 3e165a12522bb..be2fd48ee6e7b 100644 --- a/src/legacy/ui/ui_settings/routes/get.ts +++ b/src/plugins/kibana_utils/public/core/create_getter_setter.ts @@ -16,19 +16,21 @@ * specific language governing permissions and limitations * under the License. */ -import { Legacy } from 'kibana'; -async function handleRequest(request: Legacy.Request) { - const uiSettings = request.getUiSettingsService(); - return { - settings: await uiSettings.getUserProvided(), +export type Get = () => T; +export type Set = (value: T) => void; + +export const createGetterSetter = (name: string): [Get, Set] => { + let value: T; + + const get: Get = () => { + if (!value) throw new Error(`${name} was not set.`); + return value; + }; + + const set: Set = newValue => { + value = newValue; }; -} -export const getRoute = { - path: '/api/kibana/settings', - method: 'GET', - handler(request: Legacy.Request) { - return handleRequest(request); - }, + return [get, set]; }; diff --git a/src/plugins/kibana_utils/public/core/create_kibana_utils_core.test.ts b/src/plugins/kibana_utils/public/core/create_kibana_utils_core.test.ts new file mode 100644 index 0000000000000..c5b23bdf0055f --- /dev/null +++ b/src/plugins/kibana_utils/public/core/create_kibana_utils_core.test.ts @@ -0,0 +1,39 @@ +/* + * 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 { createKibanaUtilsCore } from './create_kibana_utils_core'; +import { CoreStart } from 'kibana/public'; + +describe('createKibanaUtilsCore', () => { + it('should allows to work with multiple instances', () => { + const core1 = {} as CoreStart; + const core2 = {} as CoreStart; + + const { setCoreStart: setCoreStart1, getCoreStart: getCoreStart1 } = createKibanaUtilsCore(); + const { setCoreStart: setCoreStart2, getCoreStart: getCoreStart2 } = createKibanaUtilsCore(); + + setCoreStart1(core1); + setCoreStart2(core2); + + expect(getCoreStart1()).toBe(core1); + expect(getCoreStart2()).toBe(core2); + + expect(getCoreStart1() !== getCoreStart2()).toBeTruthy(); + }); +}); diff --git a/src/plugins/kibana_utils/public/core/create_kibana_utils_core.ts b/src/plugins/kibana_utils/public/core/create_kibana_utils_core.ts new file mode 100644 index 0000000000000..84ecffa1da634 --- /dev/null +++ b/src/plugins/kibana_utils/public/core/create_kibana_utils_core.ts @@ -0,0 +1,39 @@ +/* + * 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 { createGetterSetter, Get, Set } from './create_getter_setter'; +import { CoreStart } from '../../../../core/public'; +import { KUSavedObjectClient, createSavedObjectsClient } from './saved_objects_client'; + +interface Return { + getCoreStart: Get; + setCoreStart: Set; + savedObjects: KUSavedObjectClient; +} + +export const createKibanaUtilsCore = (): Return => { + const [getCoreStart, setCoreStart] = createGetterSetter('CoreStart'); + const savedObjects = createSavedObjectsClient(getCoreStart); + + return { + getCoreStart, + setCoreStart, + savedObjects, + }; +}; diff --git a/src/plugins/kibana_utils/public/core/index.ts b/src/plugins/kibana_utils/public/core/index.ts new file mode 100644 index 0000000000000..7e8dff7191fe8 --- /dev/null +++ b/src/plugins/kibana_utils/public/core/index.ts @@ -0,0 +1,21 @@ +/* + * 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 './create_getter_setter'; +export * from './create_kibana_utils_core'; diff --git a/src/plugins/kibana_utils/public/core/saved_objects_client.ts b/src/plugins/kibana_utils/public/core/saved_objects_client.ts new file mode 100644 index 0000000000000..40407fea5d189 --- /dev/null +++ b/src/plugins/kibana_utils/public/core/saved_objects_client.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. + */ + +import { CoreStart } from '../../../../core/public'; +import { Get } from './create_getter_setter'; + +type CoreSavedObjectClient = CoreStart['savedObjects']['client']; + +export interface KUSavedObjectClient { + get: CoreSavedObjectClient['get']; +} + +export const createSavedObjectsClient = (getCoreStart: Get) => { + const savedObjectsClient: KUSavedObjectClient = { + get: (...args) => getCoreStart().savedObjects.client.get(...args), + }; + + return savedObjectsClient; +}; diff --git a/src/legacy/ui/public/registry/navbar_extensions.js b/src/plugins/kibana_utils/public/core/state.ts similarity index 79% rename from src/legacy/ui/public/registry/navbar_extensions.js rename to src/plugins/kibana_utils/public/core/state.ts index 6c7911c6f0f52..8ac6e4e0e58e6 100644 --- a/src/legacy/ui/public/registry/navbar_extensions.js +++ b/src/plugins/kibana_utils/public/core/state.ts @@ -17,12 +17,7 @@ * under the License. */ -import { uiRegistry } from './_registry'; - -export const NavBarExtensionsRegistryProvider = uiRegistry({ - name: 'navbarExtensions', - index: ['name'], - group: ['appName'], - order: ['order'] -}); +import { createGetterSetter } from './create_getter_setter'; +import { CoreStart } from '../../../../core/public'; +export const [getCoreStart, setCoreStart] = createGetterSetter('CoreStart'); diff --git a/src/plugins/kibana_utils/public/index.ts b/src/plugins/kibana_utils/public/index.ts index bac0ef629789a..7cb4d4a34e971 100644 --- a/src/plugins/kibana_utils/public/index.ts +++ b/src/plugins/kibana_utils/public/index.ts @@ -17,6 +17,7 @@ * under the License. */ +export * from './core'; export * from './store'; export * from './parse'; export * from './render_complete'; diff --git a/src/plugins/ui_actions/public/plugin.ts b/src/plugins/ui_actions/public/plugin.ts index fda25659d1226..374acaaab3999 100644 --- a/src/plugins/ui_actions/public/plugin.ts +++ b/src/plugins/ui_actions/public/plugin.ts @@ -52,5 +52,8 @@ export class UiActionsPlugin implements Plugin return this.api; } - public stop() {} + public stop() { + this.actions.clear(); + this.triggers.clear(); + } } diff --git a/src/test_utils/kbn_server.ts b/src/test_utils/kbn_server.ts index 52e2b869d5639..1494c01166e20 100644 --- a/src/test_utils/kbn_server.ts +++ b/src/test_utils/kbn_server.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - +import { Client } from 'elasticsearch'; import { ToolingLog } from '@kbn/dev-utils'; import { createLegacyEsTestCluster, @@ -36,6 +36,7 @@ import { CliArgs, Env } from '../core/server/config'; import { LegacyObjectToConfigAdapter } from '../core/server/legacy'; import { Root } from '../core/server/root'; import KbnServer from '../legacy/server/kbn_server'; +import { CallCluster } from '../legacy/core_plugins/elasticsearch'; type HttpMethod = 'delete' | 'get' | 'head' | 'post' | 'put'; @@ -147,6 +148,35 @@ export const request: Record< put: (root, path) => getSupertest(root, 'put', path), }; +export interface TestElasticsearchServer { + getStartTimeout: () => number; + start: (esArgs: string[], esEnvVars: Record) => Promise; + stop: () => Promise; + cleanup: () => Promise; + getClient: () => Client; + getCallCluster: () => CallCluster; + getUrl: () => string; +} + +export interface TestElasticsearchUtils { + stop: () => Promise; + es: TestElasticsearchServer; + hosts: string[]; + username: string; + password: string; +} + +export interface TestKibanaUtils { + root: Root; + kbnServer: KbnServer; + stop: () => Promise; +} + +export interface TestUtils { + startES: () => Promise; + startKibana: () => Promise; +} + /** * Creates an instance of the Root, including all of the core "legacy" plugins, * with default configuration tailored for unit tests, and starts es. @@ -161,7 +191,7 @@ export function createTestServers({ settings = {}, }: { adjustTimeout: (timeout: number) => void; - settings: { + settings?: { es?: { license: 'oss' | 'basic' | 'gold' | 'trial'; [key: string]: any; @@ -182,7 +212,7 @@ export function createTestServers({ */ users?: Array<{ username: string; password: string; roles: string[] }>; }; -}) { +}): TestUtils { if (!adjustTimeout) { throw new Error('adjustTimeout is required in order to avoid flaky tests'); } diff --git a/tasks/docker_docs.js b/tasks/docker_docs.js index 1c35f44eb6538..814a5a7e12bb9 100644 --- a/tasks/docker_docs.js +++ b/tasks/docker_docs.js @@ -17,7 +17,7 @@ * under the License. */ -import rimraf from 'rimraf'; +import del from 'del'; import { join } from 'path'; import { execFileSync as exec } from 'child_process'; @@ -46,7 +46,7 @@ export default function (grunt) { ], { env })).trim(); grunt.log.write('Clearing old docs ... '); - rimraf.sync(htmlDocsDir); + del.sync(htmlDocsDir); grunt.log.writeln('done'); grunt.log.write('Copying new docs ... '); diff --git a/tasks/function_test_groups.js b/tasks/function_test_groups.js index 69b998c4f229b..31656df2cb644 100644 --- a/tasks/function_test_groups.js +++ b/tasks/function_test_groups.js @@ -58,12 +58,13 @@ grunt.registerTask( const done = this.async(); try { - const stats = JSON.parse(await execa.stderr(process.execPath, [ + const result = await execa(process.execPath, [ 'scripts/functional_test_runner', ...TEST_TAGS.map(tag => `--include-tag=${tag}`), '--config', 'test/functional/config.js', '--test-stats' - ])); + ]); + const stats = JSON.parse(result.stderr); if (stats.excludedTests.length > 0) { grunt.fail.fatal(` diff --git a/test/api_integration/apis/saved_objects/find.js b/test/api_integration/apis/saved_objects/find.js index a1c84a766a116..7b2b15d298ce0 100644 --- a/test/api_integration/apis/saved_objects/find.js +++ b/test/api_integration/apis/saved_objects/find.js @@ -141,9 +141,7 @@ export default function ({ getService }) { id: '91200a00-9efd-11e7-acb3-3dab96693fab', } ], - migrationVersion: { - visualization: '7.3.1', - }, + migrationVersion: resp.body.saved_objects[0].migrationVersion, updated_at: '2017-09-21T18:51:23.794Z', version: 'WzIsMV0=', }, diff --git a/test/functional/apps/dashboard/panel_controls.js b/test/functional/apps/dashboard/panel_controls.js index b67e1dd7b2eb1..063ad1f79efae 100644 --- a/test/functional/apps/dashboard/panel_controls.js +++ b/test/functional/apps/dashboard/panel_controls.js @@ -19,7 +19,7 @@ import expect from '@kbn/expect'; -import { PIE_CHART_VIS_NAME } from '../../page_objects/dashboard_page'; +import { PIE_CHART_VIS_NAME, AREA_CHART_VIS_NAME, LINE_CHART_VIS_NAME } from '../../page_objects/dashboard_page'; import { VisualizeConstants } from '../../../../src/legacy/core_plugins/kibana/public/visualize/visualize_constants'; @@ -28,6 +28,8 @@ export default function ({ getService, getPageObjects }) { const browser = getService('browser'); const dashboardPanelActions = getService('dashboardPanelActions'); const dashboardAddPanel = getService('dashboardAddPanel'); + const dashboardReplacePanel = getService('dashboardReplacePanel'); + const dashboardVisualizations = getService('dashboardVisualizations'); const renderable = getService('renderable'); const PageObjects = getPageObjects(['dashboard', 'header', 'visualize', 'discover']); const dashboardName = 'Dashboard Panel Controls Test'; @@ -44,6 +46,62 @@ export default function ({ getService, getPageObjects }) { await PageObjects.dashboard.gotoDashboardLandingPage(); }); + describe('visualization object replace flyout', () => { + let intialDimensions; + before(async () => { + await PageObjects.dashboard.clickNewDashboard(); + await PageObjects.dashboard.setTimepickerInHistoricalDataRange(); + await dashboardAddPanel.addVisualization(PIE_CHART_VIS_NAME); + await dashboardAddPanel.addVisualization(LINE_CHART_VIS_NAME); + intialDimensions = await PageObjects.dashboard.getPanelDimensions(); + }); + + after(async function () { + await PageObjects.dashboard.gotoDashboardLandingPage(); + }); + + it('replaces old panel with selected panel', async () => { + await dashboardPanelActions.replacePanelByTitle(PIE_CHART_VIS_NAME); + await dashboardReplacePanel.replaceEmbeddable(AREA_CHART_VIS_NAME); + await PageObjects.header.waitUntilLoadingHasFinished(); + const panelTitles = await PageObjects.dashboard.getPanelTitles(); + expect(panelTitles.length).to.be(2); + expect(panelTitles[0]).to.be(AREA_CHART_VIS_NAME); + }); + + it('replaces selected visualization with old dimensions', async () => { + const newDimensions = await PageObjects.dashboard.getPanelDimensions(); + expect(intialDimensions[0]).to.eql(newDimensions[0]); + }); + + it('replaced panel persisted correctly when dashboard is hard refreshed', async () => { + const currentUrl = await browser.getCurrentUrl(); + await browser.get(currentUrl, true); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.dashboard.waitForRenderComplete(); + const panelTitles = await PageObjects.dashboard.getPanelTitles(); + expect(panelTitles.length).to.be(2); + expect(panelTitles[0]).to.be(AREA_CHART_VIS_NAME); + }); + + it('replaced panel with saved search', async () => { + const replacedSearch = 'replaced saved search'; + await dashboardVisualizations.createSavedSearch({ name: replacedSearch, fields: ['bytes', 'agent'] }); + await PageObjects.header.clickDashboard(); + const inViewMode = await PageObjects.dashboard.getIsInViewMode(); + if (inViewMode) { + await PageObjects.dashboard.switchToEditMode(); + } + await dashboardPanelActions.replacePanelByTitle(AREA_CHART_VIS_NAME); + await dashboardReplacePanel.replaceEmbeddable(replacedSearch, 'search'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.dashboard.waitForRenderComplete(); + const panelTitles = await PageObjects.dashboard.getPanelTitles(); + expect(panelTitles.length).to.be(2); + expect(panelTitles[0]).to.be(replacedSearch); + }); + }); + describe('panel edit controls', function () { before(async () => { await PageObjects.dashboard.clickNewDashboard(); @@ -67,6 +125,7 @@ export default function ({ getService, getPageObjects }) { await dashboardPanelActions.openContextMenu(); await dashboardPanelActions.expectExistsEditPanelAction(); + await dashboardPanelActions.expectExistsReplacePanelAction(); await dashboardPanelActions.expectExistsRemovePanelAction(); }); @@ -80,6 +139,7 @@ export default function ({ getService, getPageObjects }) { await dashboardPanelActions.openContextMenu(); await dashboardPanelActions.expectExistsEditPanelAction(); + await dashboardPanelActions.expectExistsReplacePanelAction(); await dashboardPanelActions.expectExistsRemovePanelAction(); // Get rid of the timestamp in the url. @@ -94,6 +154,7 @@ export default function ({ getService, getPageObjects }) { await dashboardPanelActions.clickExpandPanelToggle(); await dashboardPanelActions.openContextMenu(); await dashboardPanelActions.expectMissingEditPanelAction(); + await dashboardPanelActions.expectMissingReplacePanelAction(); await dashboardPanelActions.expectMissingRemovePanelAction(); }); @@ -101,6 +162,7 @@ export default function ({ getService, getPageObjects }) { await PageObjects.dashboard.switchToEditMode(); await dashboardPanelActions.openContextMenu(); await dashboardPanelActions.expectExistsEditPanelAction(); + await dashboardPanelActions.expectExistsReplacePanelAction(); await dashboardPanelActions.expectMissingRemovePanelAction(); await dashboardPanelActions.clickExpandPanelToggle(); }); @@ -126,13 +188,18 @@ export default function ({ getService, getPageObjects }) { }); describe('saved search object edit menu', () => { + const searchName = 'my search'; before(async () => { await PageObjects.header.clickDiscover(); - await PageObjects.discover.clickFieldListItemAdd('bytes'); - await PageObjects.discover.saveSearch('my search'); + await PageObjects.discover.clickNewSearchButton(); + await dashboardVisualizations.createSavedSearch({ name: searchName, fields: ['bytes'] }); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.header.clickDashboard(); - await dashboardAddPanel.addSavedSearch('my search'); + const inViewMode = await PageObjects.dashboard.getIsInViewMode(); + if (inViewMode) { + await PageObjects.dashboard.switchToEditMode(); + } + await dashboardAddPanel.addSavedSearch(searchName); const panelCount = await PageObjects.dashboard.getPanelCount(); expect(panelCount).to.be(1); @@ -143,7 +210,7 @@ export default function ({ getService, getPageObjects }) { await dashboardPanelActions.clickEdit(); await PageObjects.header.waitUntilLoadingHasFinished(); const queryName = await PageObjects.discover.getCurrentQueryName(); - expect(queryName).to.be('my search'); + expect(queryName).to.be(searchName); }); it('deletes the saved search when delete link is clicked', async () => { diff --git a/test/functional/page_objects/dashboard_page.js b/test/functional/page_objects/dashboard_page.js index 4f078d4b7a438..ca141114f976d 100644 --- a/test/functional/page_objects/dashboard_page.js +++ b/test/functional/page_objects/dashboard_page.js @@ -22,6 +22,7 @@ import { DashboardConstants } from '../../../src/legacy/core_plugins/kibana/publ export const PIE_CHART_VIS_NAME = 'Visualization PieChart'; export const AREA_CHART_VIS_NAME = 'Visualization漢字 AreaChart'; +export const LINE_CHART_VIS_NAME = 'Visualization漢字 LineChart'; export function DashboardPageProvider({ getService, getPageObjects }) { const log = getService('log'); @@ -499,7 +500,7 @@ export function DashboardPageProvider({ getService, getPageObjects }) { { name: 'Visualization☺ VerticalBarChart', description: 'VerticalBarChart' }, { name: AREA_CHART_VIS_NAME, description: 'AreaChart' }, { name: 'Visualization☺漢字 DataTable', description: 'DataTable' }, - { name: 'Visualization漢字 LineChart', description: 'LineChart' }, + { name: LINE_CHART_VIS_NAME, description: 'LineChart' }, { name: 'Visualization TileMap', description: 'TileMap' }, { name: 'Visualization MetricChart', description: 'MetricChart' } ]; diff --git a/test/functional/services/dashboard/index.js b/test/functional/services/dashboard/index.js index b2de690157535..bb9b861682907 100644 --- a/test/functional/services/dashboard/index.js +++ b/test/functional/services/dashboard/index.js @@ -20,5 +20,6 @@ export { DashboardVisualizationProvider } from './visualizations'; export { DashboardExpectProvider } from './expectations'; export { DashboardAddPanelProvider } from './add_panel'; +export { DashboardReplacePanelProvider } from './replace_panel'; export { DashboardPanelActionsProvider } from './panel_actions'; diff --git a/test/functional/services/dashboard/panel_actions.js b/test/functional/services/dashboard/panel_actions.js index b7327f4af6d17..6826ea1ff1715 100644 --- a/test/functional/services/dashboard/panel_actions.js +++ b/test/functional/services/dashboard/panel_actions.js @@ -19,6 +19,7 @@ const REMOVE_PANEL_DATA_TEST_SUBJ = 'embeddablePanelAction-deletePanel'; const EDIT_PANEL_DATA_TEST_SUBJ = 'embeddablePanelAction-editPanel'; +const REPLACE_PANEL_DATA_TEST_SUBJ = 'embeddablePanelAction-replacePanel'; const TOGGLE_EXPAND_PANEL_DATA_TEST_SUBJ = 'embeddablePanelAction-togglePanel'; const CUSTOMIZE_PANEL_DATA_TEST_SUBJ = 'embeddablePanelAction-CUSTOMIZE_PANEL_ACTION_ID'; const OPEN_CONTEXT_MENU_ICON_DATA_TEST_SUBJ = 'embeddablePanelToggleMenuIcon'; @@ -87,6 +88,16 @@ export function DashboardPanelActionsProvider({ getService, getPageObjects }) { await testSubjects.click(CUSTOMIZE_PANEL_DATA_TEST_SUBJ); } + async replacePanelByTitle(title) { + log.debug(`replacePanel(${title})`); + let panelOptions = null; + if (title) { + panelOptions = await this.getPanelHeading(title); + } + await this.openContextMenu(panelOptions); + await testSubjects.click(REPLACE_PANEL_DATA_TEST_SUBJ); + } + async openInspectorByTitle(title) { const header = await this.getPanelHeading(title); await this.openInspector(header); @@ -112,11 +123,21 @@ export function DashboardPanelActionsProvider({ getService, getPageObjects }) { await testSubjects.existOrFail(EDIT_PANEL_DATA_TEST_SUBJ); } + async expectExistsReplacePanelAction() { + log.debug('expectExistsEditPanelAction'); + await testSubjects.existOrFail(REPLACE_PANEL_DATA_TEST_SUBJ); + } + async expectMissingEditPanelAction() { log.debug('expectMissingEditPanelAction'); await testSubjects.missingOrFail(EDIT_PANEL_DATA_TEST_SUBJ); } + async expectMissingReplacePanelAction() { + log.debug('expectMissingEditPanelAction'); + await testSubjects.missingOrFail(REPLACE_PANEL_DATA_TEST_SUBJ); + } + async expectExistsToggleExpandAction() { log.debug('expectExistsToggleExpandAction'); await testSubjects.existOrFail(TOGGLE_EXPAND_PANEL_DATA_TEST_SUBJ); diff --git a/test/functional/services/dashboard/replace_panel.js b/test/functional/services/dashboard/replace_panel.js new file mode 100644 index 0000000000000..b3ea6f9cf21ed --- /dev/null +++ b/test/functional/services/dashboard/replace_panel.js @@ -0,0 +1,102 @@ +/* + * 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 function DashboardReplacePanelProvider({ getService }) { + const log = getService('log'); + const testSubjects = getService('testSubjects'); + const flyout = getService('flyout'); + + return new class DashboardReplacePanel { + async toggleFilterPopover() { + log.debug('DashboardReplacePanel.toggleFilter'); + await testSubjects.click('savedObjectFinderFilterButton'); + } + + async toggleFilter(type) { + log.debug(`DashboardReplacePanel.replaceToFilter(${type})`); + await this.waitForListLoading(); + await this.toggleFilterPopover(); + await testSubjects.click(`savedObjectFinderFilter-${type}`); + await this.toggleFilterPopover(); + } + + async isReplacePanelOpen() { + log.debug('DashboardReplacePanel.isReplacePanelOpen'); + return await testSubjects.exists('dashboardReplacePanel'); + } + + async ensureReplacePanelIsShowing() { + log.debug('DashboardReplacePanel.ensureReplacePanelIsShowing'); + const isOpen = await this.isReplacePanelOpen(); + if (!isOpen) { + throw new Error('Replace panel is not open, trying again.'); + } + } + + async waitForListLoading() { + await testSubjects.waitForDeleted('savedObjectFinderLoadingIndicator'); + } + + async closeReplacePanel() { + await flyout.ensureClosed('dashboardReplacePanel'); + } + + async replaceSavedSearch(searchName) { + return this.replaceEmbeddable(searchName, 'search'); + } + + async replaceSavedSearches(searches) { + for (const name of searches) { + await this.replaceSavedSearch(name); + } + } + + async replaceVisualization(vizName) { + return this.replaceEmbeddable(vizName, 'visualization'); + } + + async replaceEmbeddable(embeddableName, embeddableType) { + log.debug(`DashboardReplacePanel.replaceEmbeddable, name: ${embeddableName}, type: ${embeddableType}`); + await this.ensureReplacePanelIsShowing(); + if (embeddableType) { + await this.toggleFilter(embeddableType); + } + await this.filterEmbeddableNames(`"${embeddableName.replace('-', ' ')}"`); + await testSubjects.click(`savedObjectTitle${embeddableName.split(' ').join('-')}`); + await testSubjects.exists('addObjectToDashboardSuccess'); + await this.closeReplacePanel(); + return embeddableName; + } + + async filterEmbeddableNames(name) { + // The search input field may be disabled while the table is loading so wait for it + await this.waitForListLoading(); + await testSubjects.setValue('savedObjectFinderSearchInput', name); + await this.waitForListLoading(); + } + + async panelReplaceLinkExists(name) { + log.debug(`DashboardReplacePanel.panelReplaceLinkExists(${name})`); + await this.ensureReplacePanelIsShowing(); + await this.filterEmbeddableNames(`"${name}"`); + return await testSubjects.exists(`savedObjectTitle${name.split(' ').join('-')}`); + } + }; +} diff --git a/test/functional/services/index.ts b/test/functional/services/index.ts index 2566c3c87334c..6098e9931f299 100644 --- a/test/functional/services/index.ts +++ b/test/functional/services/index.ts @@ -24,6 +24,7 @@ import { BrowserProvider } from './browser'; import { ComboBoxProvider } from './combo_box'; import { DashboardAddPanelProvider, + DashboardReplacePanelProvider, DashboardExpectProvider, DashboardPanelActionsProvider, DashboardVisualizationProvider, @@ -66,6 +67,7 @@ export const services = { failureDebugging: FailureDebuggingProvider, visualizeListingTable: VisualizeListingTableProvider, dashboardAddPanel: DashboardAddPanelProvider, + dashboardReplacePanel: DashboardReplacePanelProvider, dashboardPanelActions: DashboardPanelActionsProvider, flyout: FlyoutProvider, comboBox: ComboBoxProvider, diff --git a/test/functional/services/remote/remote.ts b/test/functional/services/remote/remote.ts index 4dc608542c3c3..b30a0e50886d1 100644 --- a/test/functional/services/remote/remote.ts +++ b/test/functional/services/remote/remote.ts @@ -128,6 +128,8 @@ export async function RemoteProvider({ getService }: FtrProviderContext) { .manage() .window() .setRect({ width, height }); + await driver.executeScript('window.sessionStorage.clear();'); + await driver.executeScript('window.localStorage.clear();'); }); lifecycle.on('cleanup', async () => { diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json index 68641c2fd6b1f..21b49a9823f62 100644 --- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json +++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json @@ -7,7 +7,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "14.5.0", + "@elastic/eui": "14.7.0", "react": "^16.8.0", "react-dom": "^16.8.0" } diff --git a/test/plugin_functional/plugins/core_plugin_b/server/plugin.ts b/test/plugin_functional/plugins/core_plugin_b/server/plugin.ts index 67c9120b1f2d9..771e50b22f66b 100644 --- a/test/plugin_functional/plugins/core_plugin_b/server/plugin.ts +++ b/test/plugin_functional/plugins/core_plugin_b/server/plugin.ts @@ -29,7 +29,7 @@ declare module 'kibana/server' { export class CorePluginBPlugin implements Plugin { public setup(core: CoreSetup, deps: {}) { const router = core.http.createRouter(); - router.get({ path: '/core_plugin_b/', validate: false }, async (context, req, res) => { + router.get({ path: '/core_plugin_b', validate: false }, async (context, req, res) => { if (!context.pluginA) return res.internalError({ body: 'pluginA is disabled' }); const response = await context.pluginA.ping(); return res.ok({ body: `Pong via plugin A: ${response}` }); diff --git a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json index a03849129192f..7aacfbc22ceee 100644 --- a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json +++ b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json @@ -7,7 +7,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "14.5.0", + "@elastic/eui": "14.7.0", "react": "^16.8.0" } } diff --git a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json index 18b14516b9be4..80ec89fb0078a 100644 --- a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json +++ b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json @@ -8,7 +8,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "14.5.0", + "@elastic/eui": "14.7.0", "react": "^16.8.0" }, "scripts": { diff --git a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json index b147da6a448ae..54aba07079eeb 100644 --- a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json +++ b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json @@ -8,7 +8,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "14.5.0", + "@elastic/eui": "14.7.0", "react": "^16.8.0" }, "scripts": { diff --git a/test/plugin_functional/plugins/kbn_tp_top_nav/index.js b/test/plugin_functional/plugins/kbn_tp_top_nav/index.js new file mode 100644 index 0000000000000..144050beb7868 --- /dev/null +++ b/test/plugin_functional/plugins/kbn_tp_top_nav/index.js @@ -0,0 +1,30 @@ +/* + * 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 default function (kibana) { + return new kibana.Plugin({ + uiExports: { + app: { + title: 'Top Nav Menu test', + description: 'This is a sample plugin for the functional tests.', + main: 'plugins/kbn_tp_top_nav/app', + } + } + }); +} diff --git a/test/plugin_functional/plugins/kbn_tp_top_nav/package.json b/test/plugin_functional/plugins/kbn_tp_top_nav/package.json new file mode 100644 index 0000000000000..7102d24d3292d --- /dev/null +++ b/test/plugin_functional/plugins/kbn_tp_top_nav/package.json @@ -0,0 +1,9 @@ +{ + "name": "kbn_tp_top_nav", + "version": "1.0.0", + "kibana": { + "version": "kibana", + "templateVersion": "1.0.0" + }, + "license": "Apache-2.0" +} diff --git a/test/plugin_functional/plugins/kbn_tp_top_nav/public/app.js b/test/plugin_functional/plugins/kbn_tp_top_nav/public/app.js new file mode 100644 index 0000000000000..e7f97e68c086d --- /dev/null +++ b/test/plugin_functional/plugins/kbn_tp_top_nav/public/app.js @@ -0,0 +1,54 @@ +/* + * 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 { uiModules } from 'ui/modules'; +import chrome from 'ui/chrome'; + +// This is required so some default styles and required scripts/Angular modules are loaded, +// or the timezone setting is correctly applied. +import 'ui/autoload/all'; + +import { AppWithTopNav } from './top_nav'; + +const app = uiModules.get('apps/topnavDemoPlugin', ['kibana']); + +app.config($locationProvider => { + $locationProvider.html5Mode({ + enabled: false, + requireBase: false, + rewriteLinks: false, + }); +}); + +function RootController($scope, $element) { + const domNode = $element[0]; + + // render react to DOM + render(, domNode); + + // unmount react on controller destroy + $scope.$on('$destroy', () => { + unmountComponentAtNode(domNode); + }); +} + +chrome.setRootController('topnavDemoPlugin', RootController); diff --git a/test/plugin_functional/plugins/kbn_tp_top_nav/public/top_nav.tsx b/test/plugin_functional/plugins/kbn_tp_top_nav/public/top_nav.tsx new file mode 100644 index 0000000000000..d56ac5f92db88 --- /dev/null +++ b/test/plugin_functional/plugins/kbn_tp_top_nav/public/top_nav.tsx @@ -0,0 +1,54 @@ +/* + * 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, { Component } from 'react'; +import { + setup as navSetup, + start as navStart, +} from '../../../../../src/legacy/core_plugins/navigation/public/legacy'; + +const customExtension = { + id: 'registered-prop', + label: 'Registered Button', + description: 'Registered Demo', + run() {}, + testId: 'demoRegisteredNewButton', +}; + +navSetup.registerMenuItem(customExtension); + +export class AppWithTopNav extends Component { + public render() { + const { TopNavMenu } = navStart.ui; + const config = [ + { + id: 'new', + label: 'New Button', + description: 'New Demo', + run() {}, + testId: 'demoNewButton', + }, + ]; + return ( + + Hey + + ); + } +} diff --git a/test/plugin_functional/plugins/kbn_tp_top_nav/tsconfig.json b/test/plugin_functional/plugins/kbn_tp_top_nav/tsconfig.json new file mode 100644 index 0000000000000..1ba21f11b7de2 --- /dev/null +++ b/test/plugin_functional/plugins/kbn_tp_top_nav/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../../../../tsconfig.json", + "compilerOptions": { + "outDir": "./target", + "skipLibCheck": true + }, + "include": [ + "index.ts", + "public/**/*.ts", + "public/**/*.tsx", + "server/**/*.ts", + "../../../../typings/**/*", + ], + "exclude": [] +} diff --git a/test/plugin_functional/plugins/kbn_tp_visualize_embedding/package.json b/test/plugin_functional/plugins/kbn_tp_visualize_embedding/package.json index 10f122823353c..121f69ada329b 100644 --- a/test/plugin_functional/plugins/kbn_tp_visualize_embedding/package.json +++ b/test/plugin_functional/plugins/kbn_tp_visualize_embedding/package.json @@ -7,7 +7,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "14.5.0", + "@elastic/eui": "14.7.0", "react": "^16.8.0", "react-dom": "^16.8.0" } diff --git a/test/plugin_functional/plugins/ui_settings_plugin/kibana.json b/test/plugin_functional/plugins/ui_settings_plugin/kibana.json new file mode 100644 index 0000000000000..05d2dca0af937 --- /dev/null +++ b/test/plugin_functional/plugins/ui_settings_plugin/kibana.json @@ -0,0 +1,8 @@ +{ + "id": "ui_settings_plugin", + "version": "0.0.1", + "kibanaVersion": "kibana", + "configPath": ["ui_settings_plugin"], + "server": true, + "ui": true +} diff --git a/test/plugin_functional/plugins/ui_settings_plugin/package.json b/test/plugin_functional/plugins/ui_settings_plugin/package.json new file mode 100644 index 0000000000000..6a0d5999412ff --- /dev/null +++ b/test/plugin_functional/plugins/ui_settings_plugin/package.json @@ -0,0 +1,17 @@ +{ + "name": "ui_settings_plugin", + "version": "1.0.0", + "main": "target/test/plugin_functional/plugins/ui_settings_plugin", + "kibana": { + "version": "kibana", + "templateVersion": "1.0.0" + }, + "license": "Apache-2.0", + "scripts": { + "kbn": "node ../../../../scripts/kbn.js", + "build": "rm -rf './target' && tsc" + }, + "devDependencies": { + "typescript": "3.5.3" + } +} diff --git a/test/plugin_functional/plugins/ui_settings_plugin/public/index.ts b/test/plugin_functional/plugins/ui_settings_plugin/public/index.ts new file mode 100644 index 0000000000000..3c5997132d460 --- /dev/null +++ b/test/plugin_functional/plugins/ui_settings_plugin/public/index.ts @@ -0,0 +1,21 @@ +/* + * 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 { UiSettingsPlugin } from './plugin'; + +export const plugin = () => new UiSettingsPlugin(); diff --git a/test/plugin_functional/plugins/ui_settings_plugin/public/plugin.tsx b/test/plugin_functional/plugins/ui_settings_plugin/public/plugin.tsx new file mode 100644 index 0000000000000..883d203b4c37a --- /dev/null +++ b/test/plugin_functional/plugins/ui_settings_plugin/public/plugin.tsx @@ -0,0 +1,37 @@ +/* + * 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 { CoreSetup, Plugin } from 'kibana/public'; + +declare global { + interface Window { + uiSettingsPlugin?: Record; + uiSettingsPluginValue?: string; + } +} + +export class UiSettingsPlugin implements Plugin { + public setup(core: CoreSetup) { + window.uiSettingsPlugin = core.uiSettings.getAll().ui_settings_plugin; + window.uiSettingsPluginValue = core.uiSettings.get('ui_settings_plugin'); + } + + public start() {} + public stop() {} +} diff --git a/test/plugin_functional/services/index.js b/test/plugin_functional/plugins/ui_settings_plugin/server/index.ts similarity index 86% rename from test/plugin_functional/services/index.js rename to test/plugin_functional/plugins/ui_settings_plugin/server/index.ts index bf02587772f4b..7715cef31d72c 100644 --- a/test/plugin_functional/services/index.js +++ b/test/plugin_functional/plugins/ui_settings_plugin/server/index.ts @@ -17,8 +17,6 @@ * under the License. */ -import { KibanaSupertestProvider } from './supertest'; +import { UiSettingsPlugin } from './plugin'; -export const services = { - supertest: KibanaSupertestProvider, -}; +export const plugin = () => new UiSettingsPlugin(); diff --git a/test/plugin_functional/plugins/ui_settings_plugin/server/plugin.ts b/test/plugin_functional/plugins/ui_settings_plugin/server/plugin.ts new file mode 100644 index 0000000000000..c32e8a75d95da --- /dev/null +++ b/test/plugin_functional/plugins/ui_settings_plugin/server/plugin.ts @@ -0,0 +1,44 @@ +/* + * 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 { Plugin, CoreSetup } from 'kibana/server'; + +export class UiSettingsPlugin implements Plugin { + public setup(core: CoreSetup) { + core.uiSettings.register({ + ui_settings_plugin: { + name: 'from_ui_settings_plugin', + description: 'just for testing', + value: '2', + category: ['any'], + }, + }); + + const router = core.http.createRouter(); + router.get({ path: '/api/ui-settings-plugin', validate: false }, async (context, req, res) => { + const uiSettingsValue = await context.core.uiSettings.client.get( + 'ui_settings_plugin' + ); + return res.ok({ body: { uiSettingsValue } }); + }); + } + + public start() {} + public stop() {} +} diff --git a/test/plugin_functional/plugins/ui_settings_plugin/tsconfig.json b/test/plugin_functional/plugins/ui_settings_plugin/tsconfig.json new file mode 100644 index 0000000000000..1ba21f11b7de2 --- /dev/null +++ b/test/plugin_functional/plugins/ui_settings_plugin/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../../../../tsconfig.json", + "compilerOptions": { + "outDir": "./target", + "skipLibCheck": true + }, + "include": [ + "index.ts", + "public/**/*.ts", + "public/**/*.tsx", + "server/**/*.ts", + "../../../../typings/**/*", + ], + "exclude": [] +} diff --git a/test/plugin_functional/services/index.ts b/test/plugin_functional/services/index.ts new file mode 100644 index 0000000000000..dd2b25e14fe17 --- /dev/null +++ b/test/plugin_functional/services/index.ts @@ -0,0 +1,29 @@ +/* + * 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 { GenericFtrProviderContext } from '@kbn/test/types/ftr'; +import { FtrProviderContext } from 'test/functional/ftr_provider_context'; + +import { KibanaSupertestProvider } from './supertest'; + +export const services = { + supertest: KibanaSupertestProvider, +}; + +export type PluginFunctionalProviderContext = FtrProviderContext & + GenericFtrProviderContext; diff --git a/test/plugin_functional/services/supertest.js b/test/plugin_functional/services/supertest.ts similarity index 87% rename from test/plugin_functional/services/supertest.js rename to test/plugin_functional/services/supertest.ts index 390f89acaa775..6b7dc26248c06 100644 --- a/test/plugin_functional/services/supertest.js +++ b/test/plugin_functional/services/supertest.ts @@ -16,13 +16,12 @@ * specific language governing permissions and limitations * under the License. */ - - import { format as formatUrl } from 'url'; +import { FtrProviderContext } from 'test/functional/ftr_provider_context'; import supertestAsPromised from 'supertest-as-promised'; -export function KibanaSupertestProvider({ getService }) { +export function KibanaSupertestProvider({ getService }: FtrProviderContext) { const config = getService('config'); const kibanaServerUrl = formatUrl(config.get('servers.kibana')); return supertestAsPromised(kibanaServerUrl); diff --git a/test/plugin_functional/test_suites/core_plugins/applications.js b/test/plugin_functional/test_suites/core_plugins/applications.ts similarity index 86% rename from test/plugin_functional/test_suites/core_plugins/applications.js rename to test/plugin_functional/test_suites/core_plugins/applications.ts index 4c4c198d1af94..eec2ec019a515 100644 --- a/test/plugin_functional/test_suites/core_plugins/applications.js +++ b/test/plugin_functional/test_suites/core_plugins/applications.ts @@ -18,8 +18,10 @@ */ import url from 'url'; import expect from '@kbn/expect'; +import { PluginFunctionalProviderContext } from '../../services'; -export default function ({ getService, getPageObjects }) { +// eslint-disable-next-line import/no-default-export +export default function({ getService, getPageObjects }: PluginFunctionalProviderContext) { const PageObjects = getPageObjects(['common']); const browser = getService('browser'); @@ -29,16 +31,16 @@ export default function ({ getService, getPageObjects }) { const loadingScreenNotShown = async () => expect(await testSubjects.exists('kbnLoadingMessage')).to.be(false); - const loadingScreenShown = () => - testSubjects.existOrFail('kbnLoadingMessage'); + const loadingScreenShown = () => testSubjects.existOrFail('kbnLoadingMessage'); - const getKibanaUrl = (pathname, search) => url.format({ - protocol: 'http:', - hostname: process.env.TEST_KIBANA_HOST || 'localhost', - port: process.env.TEST_KIBANA_PORT || '5620', - pathname, - search, - }); + const getKibanaUrl = (pathname?: string, search?: string) => + url.format({ + protocol: 'http:', + hostname: process.env.TEST_KIBANA_HOST || 'localhost', + port: process.env.TEST_KIBANA_PORT || '5620', + pathname, + search, + }); describe('ui applications', function describeIndexTests() { before(async () => { diff --git a/test/plugin_functional/test_suites/core_plugins/index.js b/test/plugin_functional/test_suites/core_plugins/index.ts similarity index 78% rename from test/plugin_functional/test_suites/core_plugins/index.js rename to test/plugin_functional/test_suites/core_plugins/index.ts index eeb81f67751ed..bf33f37694c3a 100644 --- a/test/plugin_functional/test_suites/core_plugins/index.js +++ b/test/plugin_functional/test_suites/core_plugins/index.ts @@ -16,12 +16,16 @@ * specific language governing permissions and limitations * under the License. */ +import { PluginFunctionalProviderContext } from '../../services'; -export default function ({ loadTestFile }) { +// eslint-disable-next-line import/no-default-export +export default function({ loadTestFile }: PluginFunctionalProviderContext) { describe('core plugins', () => { loadTestFile(require.resolve('./applications')); loadTestFile(require.resolve('./legacy_plugins')); loadTestFile(require.resolve('./server_plugins')); loadTestFile(require.resolve('./ui_plugins')); + loadTestFile(require.resolve('./ui_settings')); + loadTestFile(require.resolve('./top_nav')); }); } diff --git a/test/plugin_functional/test_suites/core_plugins/legacy_plugins.js b/test/plugin_functional/test_suites/core_plugins/legacy_plugins.ts similarity index 84% rename from test/plugin_functional/test_suites/core_plugins/legacy_plugins.js rename to test/plugin_functional/test_suites/core_plugins/legacy_plugins.ts index c6edf803c9938..d0c01f1e9caf3 100644 --- a/test/plugin_functional/test_suites/core_plugins/legacy_plugins.js +++ b/test/plugin_functional/test_suites/core_plugins/legacy_plugins.ts @@ -18,13 +18,15 @@ */ import expect from '@kbn/expect'; +import { PluginFunctionalProviderContext } from '../../services'; -export default function ({ getService, getPageObjects }) { +// eslint-disable-next-line import/no-default-export +export default function({ getService, getPageObjects }: PluginFunctionalProviderContext) { const PageObjects = getPageObjects(['common']); const testSubjects = getService('testSubjects'); const supertest = getService('supertest'); - describe('legacy plugins', function describeIndexTests() { + describe('legacy plugins', () => { describe('http', () => { it('has access to New Platform HTTP service', async () => { await supertest @@ -41,7 +43,7 @@ export default function ({ getService, getPageObjects }) { }); }); - describe('application service compatibility layer', function describeIndexTests() { + describe('application service compatibility layer', () => { it('can render legacy apps', async () => { await PageObjects.common.navigateToApp('core_plugin_legacy'); expect(await testSubjects.exists('coreLegacyCompatH1')).to.be(true); diff --git a/test/plugin_functional/test_suites/core_plugins/server_plugins.js b/test/plugin_functional/test_suites/core_plugins/server_plugins.ts similarity index 67% rename from test/plugin_functional/test_suites/core_plugins/server_plugins.js rename to test/plugin_functional/test_suites/core_plugins/server_plugins.ts index a17b19468d6a8..3881af5642996 100644 --- a/test/plugin_functional/test_suites/core_plugins/server_plugins.js +++ b/test/plugin_functional/test_suites/core_plugins/server_plugins.ts @@ -16,20 +16,18 @@ * specific language governing permissions and limitations * under the License. */ +import { PluginFunctionalProviderContext } from '../../services'; -import expect from '@kbn/expect'; - -export default function ({ getService, getPageObjects }) { - const PageObjects = getPageObjects(['common']); - const browser = getService('browser'); +// eslint-disable-next-line import/no-default-export +export default function({ getService }: PluginFunctionalProviderContext) { + const supertest = getService('supertest'); describe('server plugins', function describeIndexTests() { it('extend request handler context', async () => { - const url = `${PageObjects.common.getHostPort()}/core_plugin_b/`; - await browser.get(url); - - const pageSource = await browser.execute('return window.document.body.textContent;'); - expect(pageSource).to.equal('Pong via plugin A: true'); + await supertest + .get('/core_plugin_b') + .expect(200) + .expect('Pong via plugin A: true'); }); }); } diff --git a/test/plugin_functional/test_suites/core_plugins/top_nav.js b/test/plugin_functional/test_suites/core_plugins/top_nav.js new file mode 100644 index 0000000000000..5c46e3d7f76db --- /dev/null +++ b/test/plugin_functional/test_suites/core_plugins/top_nav.js @@ -0,0 +1,40 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import expect from '@kbn/expect'; + +export default function ({ getService, getPageObjects }) { + const PageObjects = getPageObjects(['common']); + + const browser = getService('browser'); + const testSubjects = getService('testSubjects'); + + describe.skip('top nav', function describeIndexTests() { + before(async () => { + const url = `${PageObjects.common.getHostPort()}/app/kbn_tp_top_nav/`; + await browser.get(url); + }); + + it('Shows registered menu items', async () => { + const ownMenuItem = await testSubjects.find('demoNewButton'); + expect(await ownMenuItem.getVisibleText()).to.be('New Button'); + const demoRegisteredNewButton = await testSubjects.find('demoRegisteredNewButton'); + expect(await demoRegisteredNewButton.getVisibleText()).to.be('Registered Button'); + }); + }); +} diff --git a/test/plugin_functional/test_suites/core_plugins/ui_plugins.js b/test/plugin_functional/test_suites/core_plugins/ui_plugins.ts similarity index 84% rename from test/plugin_functional/test_suites/core_plugins/ui_plugins.js rename to test/plugin_functional/test_suites/core_plugins/ui_plugins.ts index 15a4dcabddbd1..a971921ad3ed8 100644 --- a/test/plugin_functional/test_suites/core_plugins/ui_plugins.js +++ b/test/plugin_functional/test_suites/core_plugins/ui_plugins.ts @@ -18,12 +18,14 @@ */ import expect from '@kbn/expect'; +import { PluginFunctionalProviderContext } from '../../services'; -export default function ({ getService, getPageObjects }) { +// eslint-disable-next-line import/no-default-export +export default function({ getService, getPageObjects }: PluginFunctionalProviderContext) { const PageObjects = getPageObjects(['common']); const browser = getService('browser'); - describe('ui plugins', function () { + describe('ui plugins', function() { describe('loading', function describeIndexTests() { before(async () => { await PageObjects.common.navigateToApp('settings'); @@ -40,7 +42,9 @@ export default function ({ getService, getPageObjects }) { }); it('should attach string to window.corePluginB', async () => { - const hasAccessToInjectedMetadata = await browser.execute('return window.hasAccessToInjectedMetadata'); + const hasAccessToInjectedMetadata = await browser.execute( + 'return window.hasAccessToInjectedMetadata' + ); expect(hasAccessToInjectedMetadata).to.equal(true); }); }); @@ -50,7 +54,7 @@ export default function ({ getService, getPageObjects }) { }); it('should attach pluginContext to window.corePluginB', async () => { - const envData = await browser.execute('return window.env'); + const envData: any = await browser.execute('return window.env'); expect(envData.mode.dev).to.be(true); expect(envData.packageInfo.version).to.be.a('string'); }); diff --git a/test/plugin_functional/test_suites/core_plugins/ui_settings.ts b/test/plugin_functional/test_suites/core_plugins/ui_settings.ts new file mode 100644 index 0000000000000..2b4227ee798e3 --- /dev/null +++ b/test/plugin_functional/test_suites/core_plugins/ui_settings.ts @@ -0,0 +1,52 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import expect from '@kbn/expect'; +import { PluginFunctionalProviderContext } from '../../services'; + +// eslint-disable-next-line import/no-default-export +export default function({ getService, getPageObjects }: PluginFunctionalProviderContext) { + const PageObjects = getPageObjects(['common']); + const browser = getService('browser'); + const supertest = getService('supertest'); + + describe('ui settings', function() { + before(async () => { + await PageObjects.common.navigateToApp('settings'); + }); + + it('client plugins have access to registered settings', async () => { + const settings = await browser.execute('return window.uiSettingsPlugin'); + expect(settings).to.eql({ + category: ['any'], + description: 'just for testing', + name: 'from_ui_settings_plugin', + value: '2', + }); + const settingsValue = await browser.execute('return window.uiSettingsPluginValue'); + expect(settingsValue).to.be('2'); + }); + + it('server plugins have access to registered settings', async () => { + await supertest + .get('/api/ui-settings-plugin') + .expect(200) + .expect({ uiSettingsValue: 2 }); + }); + }); +} diff --git a/test/scripts/jenkins_build_kbn_tp_sample_panel_action.sh b/test/scripts/jenkins_build_kbn_tp_sample_panel_action.sh new file mode 100755 index 0000000000000..4b16e3b32fefd --- /dev/null +++ b/test/scripts/jenkins_build_kbn_tp_sample_panel_action.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +source src/dev/ci_setup/setup_env.sh + +cd test/plugin_functional/plugins/kbn_tp_sample_panel_action; +if [[ ! -d "target" ]]; then + checks-reporter-with-killswitch "Build kbn_tp_sample_panel_action" yarn build; +fi +cd -; diff --git a/test/scripts/jenkins_ci_group.sh b/test/scripts/jenkins_ci_group.sh index 6deddd5a59152..274cca695b535 100755 --- a/test/scripts/jenkins_ci_group.sh +++ b/test/scripts/jenkins_ci_group.sh @@ -22,10 +22,7 @@ fi checks-reporter-with-killswitch "Functional tests / Group ${CI_GROUP}" yarn run grunt "run:functionalTests_ciGroup${CI_GROUP}"; if [ "$CI_GROUP" == "1" ]; then - # build kbn_tp_sample_panel_action - cd test/plugin_functional/plugins/kbn_tp_sample_panel_action; - checks-reporter-with-killswitch "Build kbn_tp_sample_panel_action" yarn build; - cd -; + source test/scripts/jenkins_build_kbn_tp_sample_panel_action.sh yarn run grunt run:pluginFunctionalTestsRelease --from=source; yarn run grunt run:interpreterFunctionalTestsRelease; fi diff --git a/vars/kibanaPipeline.groovy b/vars/kibanaPipeline.groovy new file mode 100644 index 0000000000000..e8d7cc03edad0 --- /dev/null +++ b/vars/kibanaPipeline.groovy @@ -0,0 +1,251 @@ +def withWorkers(name, preWorkerClosure = {}, workerClosures = [:]) { + return { + jobRunner('tests-xl', true) { + try { + doSetup() + preWorkerClosure() + + def nextWorker = 1 + def worker = { workerClosure -> + def workerNumber = nextWorker + nextWorker++ + + return { + workerClosure(workerNumber) + } + } + + def workers = [:] + workerClosures.each { workerName, workerClosure -> + workers[workerName] = worker(workerClosure) + } + + parallel(workers) + } finally { + catchError { + uploadAllGcsArtifacts(name) + } + + catchError { + runbld.junit() + } + + catchError { + publishJunit() + } + + catchError { + runErrorReporter() + } + } + } + } +} + +def getPostBuildWorker(name, closure) { + return { workerNumber -> + def kibanaPort = "61${workerNumber}1" + def esPort = "61${workerNumber}2" + def esTransportPort = "61${workerNumber}3" + + withEnv([ + "CI_WORKER_NUMBER=${workerNumber}", + "TEST_KIBANA_HOST=localhost", + "TEST_KIBANA_PORT=${kibanaPort}", + "TEST_KIBANA_URL=http://elastic:changeme@localhost:${kibanaPort}", + "TEST_ES_URL=http://elastic:changeme@localhost:${esPort}", + "TEST_ES_TRANSPORT_PORT=${esTransportPort}", + "IS_PIPELINE_JOB=1", + ]) { + closure() + } + } +} + +def getOssCiGroupWorker(ciGroup) { + return getPostBuildWorker("ciGroup" + ciGroup, { + withEnv([ + "CI_GROUP=${ciGroup}", + "JOB=kibana-ciGroup${ciGroup}", + ]) { + runbld "./test/scripts/jenkins_ci_group.sh" + } + }) +} + +def getXpackCiGroupWorker(ciGroup) { + return getPostBuildWorker("xpack-ciGroup" + ciGroup, { + withEnv([ + "CI_GROUP=${ciGroup}", + "JOB=xpack-kibana-ciGroup${ciGroup}", + ]) { + runbld "./test/scripts/jenkins_xpack_ci_group.sh" + } + }) +} + +def legacyJobRunner(name) { + return { + parallel([ + "${name}": { + withEnv([ + "JOB=${name}", + ]) { + jobRunner('linux && immutable', false) { + try { + runbld('.ci/run.sh', true) + } finally { + catchError { + uploadAllGcsArtifacts(name) + } + catchError { + publishJunit() + } + catchError { + runErrorReporter() + } + } + } + } + } + ]) + } +} + +def jobRunner(label, useRamDisk, closure) { + node(label) { + if (useRamDisk) { + // Move to a temporary workspace, so that we can symlink the real workspace into /dev/shm + def originalWorkspace = env.WORKSPACE + ws('/tmp/workspace') { + sh """ + mkdir -p /dev/shm/workspace + mkdir -p '${originalWorkspace}' # create all of the directories leading up to the workspace, if they don't exist + rm --preserve-root -rf '${originalWorkspace}' # then remove just the workspace, just in case there's stuff in it + ln -s /dev/shm/workspace '${originalWorkspace}' + """ + } + } + + def scmVars = checkout scm + + withEnv([ + "CI=true", + "HOME=${env.JENKINS_HOME}", + "PR_SOURCE_BRANCH=${env.ghprbSourceBranch ?: ''}", + "PR_TARGET_BRANCH=${env.ghprbTargetBranch ?: ''}", + "PR_AUTHOR=${env.ghprbPullAuthorLogin ?: ''}", + "TEST_BROWSER_HEADLESS=1", + "GIT_BRANCH=${scmVars.GIT_BRANCH}", + ]) { + withCredentials([ + string(credentialsId: 'vault-addr', variable: 'VAULT_ADDR'), + string(credentialsId: 'vault-role-id', variable: 'VAULT_ROLE_ID'), + string(credentialsId: 'vault-secret-id', variable: 'VAULT_SECRET_ID'), + ]) { + // scm is configured to check out to the ./kibana directory + dir('kibana') { + closure() + } + } + } + } +} + +// TODO what should happen if GCS, Junit, or email publishing fails? Unstable build? Failed build? + +def uploadGcsArtifact(workerName, pattern) { + def storageLocation = "gs://kibana-ci-artifacts/jobs/${env.JOB_NAME}/${BUILD_NUMBER}/${workerName}" // TODO + + googleStorageUpload( + credentialsId: 'kibana-ci-gcs-plugin', + bucket: storageLocation, + pattern: pattern, + sharedPublicly: true, + showInline: true, + ) +} + +def uploadAllGcsArtifacts(workerName) { + def ARTIFACT_PATTERNS = [ + 'target/kibana-*', + 'target/junit/**/*', + 'test/**/screenshots/**/*.png', + 'test/functional/failure_debug/html/*.html', + 'x-pack/test/**/screenshots/**/*.png', + 'x-pack/test/functional/failure_debug/html/*.html', + 'x-pack/test/functional/apps/reporting/reports/session/*.pdf', + ] + + ARTIFACT_PATTERNS.each { pattern -> + uploadGcsArtifact(workerName, pattern) + } +} + +def publishJunit() { + junit(testResults: 'target/junit/**/*.xml', allowEmptyResults: true, keepLongStdio: true) +} + +def sendMail() { + // If the build doesn't have a result set by this point, there haven't been any errors and it can be marked as a success + // The e-mail plugin for the infra e-mail depends upon this being set + currentBuild.result = currentBuild.result ?: 'SUCCESS' + + def buildStatus = buildUtils.getBuildStatus() + if (buildStatus != 'SUCCESS' && buildStatus != 'ABORTED') { + node('flyweight') { + sendInfraMail() + sendKibanaMail() + } + } +} + +def sendInfraMail() { + catchError { + step([ + $class: 'Mailer', + notifyEveryUnstableBuild: true, + recipients: 'infra-root+build@elastic.co', + sendToIndividuals: false + ]) + } +} + +def sendKibanaMail() { + catchError { + def buildStatus = buildUtils.getBuildStatus() + if(params.NOTIFY_ON_FAILURE && buildStatus != 'SUCCESS' && buildStatus != 'ABORTED') { + emailext( + to: 'build-kibana@elastic.co', + subject: "${env.JOB_NAME} - Build # ${env.BUILD_NUMBER} - ${buildStatus}", + body: '${SCRIPT,template="groovy-html.template"}', + mimeType: 'text/html', + ) + } + } +} + +def bash(script) { + sh "#!/bin/bash\n${script}" +} + +def doSetup() { + runbld "./test/scripts/jenkins_setup.sh" +} + +def buildOss() { + runbld "./test/scripts/jenkins_build_kibana.sh" +} + +def buildXpack() { + runbld "./test/scripts/jenkins_xpack_build_kibana.sh" +} + +def runErrorReporter() { + bash """ + source src/dev/ci_setup/setup_env.sh + node scripts/report_failed_tests + """ +} + +return this diff --git a/vars/runbld.groovy b/vars/runbld.groovy new file mode 100644 index 0000000000000..501e2421ca65b --- /dev/null +++ b/vars/runbld.groovy @@ -0,0 +1,11 @@ +def call(script, enableJunitProcessing = false) { + def extraConfig = enableJunitProcessing ? "" : "--config ${env.WORKSPACE}/kibana/.ci/runbld_no_junit.yml" + + sh "/usr/local/bin/runbld -d '${pwd()}' ${extraConfig} ${script}" +} + +def junit() { + sh "/usr/local/bin/runbld -d '${pwd()}' ${env.WORKSPACE}/kibana/test/scripts/jenkins_runbld_junit.sh" +} + +return this diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index 735ee0b6b67b5..0ee50c0caa340 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -7,7 +7,6 @@ "xpack.apm": "legacy/plugins/apm", "xpack.beatsManagement": "legacy/plugins/beats_management", "xpack.canvas": "legacy/plugins/canvas", - "xpack.code": ["legacy/plugins/code", "plugins/code"], "xpack.crossClusterReplication": "legacy/plugins/cross_cluster_replication", "xpack.dashboardMode": "legacy/plugins/dashboard_mode", "xpack.features": "plugins/features", @@ -31,7 +30,7 @@ "xpack.rollupJobs": "legacy/plugins/rollup", "xpack.searchProfiler": "legacy/plugins/searchprofiler", "xpack.siem": "legacy/plugins/siem", - "xpack.security": "legacy/plugins/security", + "xpack.security": ["legacy/plugins/security", "plugins/security"], "xpack.server": "legacy/server", "xpack.snapshotRestore": "legacy/plugins/snapshot_restore", "xpack.spaces": ["legacy/plugins/spaces", "plugins/spaces"], diff --git a/x-pack/dev-tools/jest/setup/polyfills.js b/x-pack/dev-tools/jest/setup/polyfills.js index 8e5c5a8025b82..566e4701eeaac 100644 --- a/x-pack/dev-tools/jest/setup/polyfills.js +++ b/x-pack/dev-tools/jest/setup/polyfills.js @@ -14,5 +14,6 @@ bluebird.Promise.setScheduler(function (fn) { global.setImmediate.call(global, f const MutationObserver = require('mutation-observer'); Object.defineProperty(window, 'MutationObserver', { value: MutationObserver }); +require('whatwg-fetch'); const URL = { createObjectURL: () => '' }; Object.defineProperty(window, 'URL', { value: URL }); diff --git a/x-pack/index.js b/x-pack/index.js index 756d9b4d3127a..2b467d2525d9b 100644 --- a/x-pack/index.js +++ b/x-pack/index.js @@ -18,7 +18,6 @@ import { dashboardMode } from './legacy/plugins/dashboard_mode'; import { logstash } from './legacy/plugins/logstash'; import { beats } from './legacy/plugins/beats_management'; import { apm } from './legacy/plugins/apm'; -import { code } from './legacy/plugins/code'; import { maps } from './legacy/plugins/maps'; import { licenseManagement } from './legacy/plugins/license_management'; import { cloud } from './legacy/plugins/cloud'; @@ -62,7 +61,6 @@ module.exports = function (kibana) { logstash(kibana), beats(kibana), apm(kibana), - code(kibana), maps(kibana), canvas(kibana), licenseManagement(kibana), diff --git a/x-pack/legacy/plugins/actions/server/actions_client.test.ts b/x-pack/legacy/plugins/actions/server/actions_client.test.ts index b582d9f2a1b0d..b4940b23ba61c 100644 --- a/x-pack/legacy/plugins/actions/server/actions_client.test.ts +++ b/x-pack/legacy/plugins/actions/server/actions_client.test.ts @@ -11,9 +11,9 @@ import { ActionsClient } from './actions_client'; import { ExecutorType } from './types'; import { ActionExecutor, TaskRunnerFactory } from './lib'; import { taskManagerMock } from '../../task_manager/task_manager.mock'; -import { SavedObjectsClientMock } from '../../../../../src/core/server/mocks'; +import { savedObjectsClientMock } from '../../../../../src/core/server/mocks'; -const savedObjectsClient = SavedObjectsClientMock.create(); +const savedObjectsClient = savedObjectsClientMock.create(); const mockTaskManager = taskManagerMock.create(); diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/email.test.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/email.test.ts index 4e2ef29dd740f..8e6b1f19b172c 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/email.test.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/email.test.ts @@ -10,7 +10,7 @@ jest.mock('./lib/send_email', () => ({ import { ActionType, ActionTypeExecutorOptions } from '../types'; import { validateConfig, validateSecrets, validateParams } from '../lib'; -import { SavedObjectsClientMock } from '../../../../../../src/core/server/mocks'; +import { savedObjectsClientMock } from '../../../../../../src/core/server/mocks'; import { createActionTypeRegistry } from './index.test'; import { sendEmail } from './lib/send_email'; import { ActionParamsType, ActionTypeConfigType, ActionTypeSecretsType } from './email'; @@ -23,7 +23,7 @@ const NO_OP_FN = () => {}; const services = { log: NO_OP_FN, callCluster: async (path: string, opts: any) => {}, - savedObjectsClient: SavedObjectsClientMock.create(), + savedObjectsClient: savedObjectsClientMock.create(), }; let actionType: ActionType; diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/es_index.test.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/es_index.test.ts index 57a107968ba70..35d81ba74fa72 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/es_index.test.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/es_index.test.ts @@ -10,7 +10,7 @@ jest.mock('./lib/send_email', () => ({ import { ActionType, ActionTypeExecutorOptions } from '../types'; import { validateConfig, validateParams } from '../lib'; -import { SavedObjectsClientMock } from '../../../../../../src/core/server/mocks'; +import { savedObjectsClientMock } from '../../../../../../src/core/server/mocks'; import { createActionTypeRegistry } from './index.test'; import { ActionParamsType, ActionTypeConfigType } from './es_index'; @@ -20,7 +20,7 @@ const NO_OP_FN = () => {}; const services = { log: NO_OP_FN, callCluster: jest.fn(), - savedObjectsClient: SavedObjectsClientMock.create(), + savedObjectsClient: savedObjectsClientMock.create(), }; let actionType: ActionType; diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.test.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.test.ts index a9f3ea757e33b..1d453d2bd2340 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.test.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.test.ts @@ -10,7 +10,7 @@ jest.mock('./lib/post_pagerduty', () => ({ import { ActionType, Services, ActionTypeExecutorOptions } from '../types'; import { validateConfig, validateSecrets, validateParams } from '../lib'; -import { SavedObjectsClientMock } from '../../../../../../src/core/server/mocks'; +import { savedObjectsClientMock } from '../../../../../../src/core/server/mocks'; import { postPagerduty } from './lib/post_pagerduty'; import { createActionTypeRegistry } from './index.test'; @@ -20,7 +20,7 @@ const ACTION_TYPE_ID = '.pagerduty'; const services: Services = { callCluster: async (path: string, opts: any) => {}, - savedObjectsClient: SavedObjectsClientMock.create(), + savedObjectsClient: savedObjectsClientMock.create(), }; let actionType: ActionType; diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/server_log.test.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/server_log.test.ts index e3feec6d1bc67..76e639c994834 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/server_log.test.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/server_log.test.ts @@ -7,7 +7,7 @@ import { ActionType } from '../types'; import { validateParams } from '../lib'; import { Logger } from '../../../../../../src/core/server'; -import { SavedObjectsClientMock } from '../../../../../../src/core/server/mocks'; +import { savedObjectsClientMock } from '../../../../../../src/core/server/mocks'; import { createActionTypeRegistry } from './index.test'; const ACTION_TYPE_ID = '.server-log'; @@ -92,7 +92,7 @@ describe('execute()', () => { actionId, services: { callCluster: async (path: string, opts: any) => {}, - savedObjectsClient: SavedObjectsClientMock.create(), + savedObjectsClient: savedObjectsClientMock.create(), }, params: { message: 'message text here', level: 'info' }, config: {}, diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/slack.test.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/slack.test.ts index 681f508b1d214..f6bd2d2b254df 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/slack.test.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/slack.test.ts @@ -6,7 +6,7 @@ import { ActionType, Services, ActionTypeExecutorOptions } from '../types'; import { ActionTypeRegistry } from '../action_type_registry'; -import { SavedObjectsClientMock } from '../../../../../../src/core/server/mocks'; +import { savedObjectsClientMock } from '../../../../../../src/core/server/mocks'; import { ActionExecutor, validateParams, validateSecrets, TaskRunnerFactory } from '../lib'; import { getActionType } from './slack'; import { taskManagerMock } from '../../../task_manager/task_manager.mock'; @@ -15,7 +15,7 @@ const ACTION_TYPE_ID = '.slack'; const services: Services = { callCluster: async (path: string, opts: any) => {}, - savedObjectsClient: SavedObjectsClientMock.create(), + savedObjectsClient: savedObjectsClientMock.create(), }; let actionTypeRegistry: ActionTypeRegistry; diff --git a/x-pack/legacy/plugins/actions/server/create_execute_function.test.ts b/x-pack/legacy/plugins/actions/server/create_execute_function.test.ts index e928eb491c1b5..c6817b3bc12f3 100644 --- a/x-pack/legacy/plugins/actions/server/create_execute_function.test.ts +++ b/x-pack/legacy/plugins/actions/server/create_execute_function.test.ts @@ -6,10 +6,10 @@ import { taskManagerMock } from '../../task_manager/task_manager.mock'; import { createExecuteFunction } from './create_execute_function'; -import { SavedObjectsClientMock } from '../../../../../src/core/server/mocks'; +import { savedObjectsClientMock } from '../../../../../src/core/server/mocks'; const mockTaskManager = taskManagerMock.create(); -const savedObjectsClient = SavedObjectsClientMock.create(); +const savedObjectsClient = savedObjectsClientMock.create(); const getBasePath = jest.fn(); beforeEach(() => jest.resetAllMocks()); diff --git a/x-pack/legacy/plugins/actions/server/lib/action_executor.test.ts b/x-pack/legacy/plugins/actions/server/lib/action_executor.test.ts index 661a08df3dc30..c724717bef9eb 100644 --- a/x-pack/legacy/plugins/actions/server/lib/action_executor.test.ts +++ b/x-pack/legacy/plugins/actions/server/lib/action_executor.test.ts @@ -10,12 +10,12 @@ import { ActionExecutor } from './action_executor'; import { actionTypeRegistryMock } from '../action_type_registry.mock'; import { encryptedSavedObjectsMock } from '../../../encrypted_saved_objects/server/plugin.mock'; import { - SavedObjectsClientMock, + savedObjectsClientMock, loggingServiceMock, } from '../../../../../../src/core/server/mocks'; const actionExecutor = new ActionExecutor(); -const savedObjectsClient = SavedObjectsClientMock.create(); +const savedObjectsClient = savedObjectsClientMock.create(); function getServices() { return { diff --git a/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.test.ts b/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.test.ts index cc18c7b169429..6411bc7462c9f 100644 --- a/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.test.ts +++ b/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.test.ts @@ -13,7 +13,7 @@ import { actionTypeRegistryMock } from '../action_type_registry.mock'; import { actionExecutorMock } from './action_executor.mock'; import { encryptedSavedObjectsMock } from '../../../encrypted_saved_objects/server/plugin.mock'; import { - SavedObjectsClientMock, + savedObjectsClientMock, loggingServiceMock, } from '../../../../../../src/core/server/mocks'; @@ -54,7 +54,7 @@ afterAll(() => fakeTimer.restore()); const services = { log: jest.fn(), callCluster: jest.fn(), - savedObjectsClient: SavedObjectsClientMock.create(), + savedObjectsClient: savedObjectsClientMock.create(), }; const actionExecutorInitializerParams = { logger: loggingServiceMock.create().get(), diff --git a/x-pack/legacy/plugins/actions/server/routes/_mock_server.ts b/x-pack/legacy/plugins/actions/server/routes/_mock_server.ts index 340d341a5ef14..7f2341c1aa010 100644 --- a/x-pack/legacy/plugins/actions/server/routes/_mock_server.ts +++ b/x-pack/legacy/plugins/actions/server/routes/_mock_server.ts @@ -5,7 +5,7 @@ */ import Hapi from 'hapi'; -import { SavedObjectsClientMock } from '../../../../../../src/core/server/mocks'; +import { savedObjectsClientMock } from '../../../../../../src/core/server/mocks'; import { actionsClientMock } from '../actions_client.mock'; import { actionTypeRegistryMock } from '../action_type_registry.mock'; import { encryptedSavedObjectsMock } from '../../../encrypted_saved_objects/server/plugin.mock'; @@ -21,7 +21,7 @@ export function createMockServer(config: Record = defaultConfig) { const actionsClient = actionsClientMock.create(); const actionTypeRegistry = actionTypeRegistryMock.create(); - const savedObjectsClient = SavedObjectsClientMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); const encryptedSavedObjects = encryptedSavedObjectsMock.create(); server.config = () => { diff --git a/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts b/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts index 574aed3fe9329..093f5f7484004 100644 --- a/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts +++ b/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts @@ -6,13 +6,13 @@ import { schema } from '@kbn/config-schema'; import { AlertsClient } from './alerts_client'; -import { SavedObjectsClientMock, loggingServiceMock } from '../../../../../src/core/server/mocks'; +import { savedObjectsClientMock, loggingServiceMock } from '../../../../../src/core/server/mocks'; import { taskManagerMock } from '../../task_manager/task_manager.mock'; import { alertTypeRegistryMock } from './alert_type_registry.mock'; const taskManager = taskManagerMock.create(); const alertTypeRegistry = alertTypeRegistryMock.create(); -const savedObjectsClient = SavedObjectsClientMock.create(); +const savedObjectsClient = savedObjectsClientMock.create(); const alertsClientParams = { taskManager, diff --git a/x-pack/legacy/plugins/alerting/server/lib/task_runner_factory.test.ts b/x-pack/legacy/plugins/alerting/server/lib/task_runner_factory.test.ts index ccc91ae2a9034..23591692bca1f 100644 --- a/x-pack/legacy/plugins/alerting/server/lib/task_runner_factory.test.ts +++ b/x-pack/legacy/plugins/alerting/server/lib/task_runner_factory.test.ts @@ -11,7 +11,7 @@ import { ConcreteTaskInstance } from '../../../task_manager'; import { TaskRunnerContext, TaskRunnerFactory } from './task_runner_factory'; import { encryptedSavedObjectsMock } from '../../../encrypted_saved_objects/server/plugin.mock'; import { - SavedObjectsClientMock, + savedObjectsClientMock, loggingServiceMock, } from '../../../../../../src/core/server/mocks'; @@ -51,7 +51,7 @@ beforeAll(() => { afterAll(() => fakeTimer.restore()); -const savedObjectsClient = SavedObjectsClientMock.create(); +const savedObjectsClient = savedObjectsClientMock.create(); const encryptedSavedObjectsPlugin = encryptedSavedObjectsMock.create(); const services = { log: jest.fn(), diff --git a/x-pack/legacy/plugins/apm/common/projections/typings.ts b/x-pack/legacy/plugins/apm/common/projections/typings.ts index 0c56fd2f75bde..2b55395b70c6b 100644 --- a/x-pack/legacy/plugins/apm/common/projections/typings.ts +++ b/x-pack/legacy/plugins/apm/common/projections/typings.ts @@ -4,15 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SearchParams } from 'elasticsearch'; +import { ESSearchRequest, ESSearchBody } from '../../typings/elasticsearch'; +import { + AggregationOptionsByType, + AggregationInputMap +} from '../../typings/elasticsearch/aggregations'; -export type Projection = Omit & { - body: { - query: any; - } & { +export type Projection = Omit & { + body: Omit & { aggs?: { [key: string]: { - terms: any; + terms: AggregationOptionsByType['terms']; + aggs?: AggregationInputMap; }; }; }; diff --git a/x-pack/legacy/plugins/apm/common/projections/util/merge_projection/index.test.ts b/x-pack/legacy/plugins/apm/common/projections/util/merge_projection/index.test.ts index ae1b7c552ab4e..aa72b5fd71365 100644 --- a/x-pack/legacy/plugins/apm/common/projections/util/merge_projection/index.test.ts +++ b/x-pack/legacy/plugins/apm/common/projections/util/merge_projection/index.test.ts @@ -29,12 +29,15 @@ describe('mergeProjection', () => { }); it('merges plain objects', () => { + const termsAgg = { terms: { field: 'bar' } }; expect( mergeProjection( - { body: { query: {}, aggs: { foo: { terms: { field: 'bar' } } } } }, + { body: { query: {}, aggs: { foo: termsAgg } } }, { body: { - aggs: { foo: { aggs: { bar: { terms: { field: 'baz' } } } } } + aggs: { + foo: { ...termsAgg, aggs: { bar: { terms: { field: 'baz' } } } } + } } } ) diff --git a/x-pack/legacy/plugins/apm/common/projections/util/merge_projection/index.ts b/x-pack/legacy/plugins/apm/common/projections/util/merge_projection/index.ts index 5b6b5b0b7f058..9a8f11c6493c5 100644 --- a/x-pack/legacy/plugins/apm/common/projections/util/merge_projection/index.ts +++ b/x-pack/legacy/plugins/apm/common/projections/util/merge_projection/index.ts @@ -4,10 +4,22 @@ * you may not use this file except in compliance with the Elastic License. */ import { merge, isPlainObject } from 'lodash'; +import { DeepPartial } from 'utility-types'; +import { AggregationInputMap } from '../../../../typings/elasticsearch/aggregations'; +import { + ESSearchRequest, + ESSearchBody +} from '../../../../typings/elasticsearch'; import { Projection } from '../../typings'; type PlainObject = Record; +type SourceProjection = Omit, 'body'> & { + body: Omit, 'aggs'> & { + aggs?: AggregationInputMap; + }; +}; + type DeepMerge = U extends PlainObject ? (T extends PlainObject ? (Omit & @@ -19,10 +31,10 @@ type DeepMerge = U extends PlainObject : U) : U; -export function mergeProjection( - target: T, - source: U -): DeepMerge { +export function mergeProjection< + T extends Projection, + U extends SourceProjection +>(target: T, source: U): DeepMerge { return merge({}, target, source, (a, b) => { if (isPlainObject(a) && isPlainObject(b)) { return undefined; diff --git a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/__snapshots__/List.test.tsx.snap b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/__snapshots__/List.test.tsx.snap index 2689ce8bd9424..c663c52d7d639 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/__snapshots__/List.test.tsx.snap +++ b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/__snapshots__/List.test.tsx.snap @@ -132,7 +132,11 @@ exports[`ErrorGroupOverview -> List should render empty state 1`] = ` data-test-subj="tableHeaderCell_groupId_0" role="columnheader" scope="col" - width="96px" + style={ + Object { + "width": "96px", + } + } >
List should render empty state 1`] = ` data-test-subj="tableHeaderCell_message_1" role="columnheader" scope="col" - width="50%" + style={ + Object { + "width": "50%", + } + } >
List should render empty state 1`] = ` data-test-subj="tableHeaderCell_handled_2" role="columnheader" scope="col" + style={ + Object { + "width": undefined, + } + } >
List should render empty state 1`] = ` data-test-subj="tableHeaderCell_occurrenceCount_3" role="columnheader" scope="col" + style={ + Object { + "width": undefined, + } + } > - - - - - - - - - -
- 4ba67b8 -
-
-
-
-
-
- - - - - -
- -
- -
- - -`; diff --git a/x-pack/legacy/plugins/code/public/components/commits/commit.test.tsx b/x-pack/legacy/plugins/code/public/components/commits/commit.test.tsx deleted file mode 100644 index 1c87f7cc72235..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/commits/commit.test.tsx +++ /dev/null @@ -1,36 +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 React from 'react'; -import { mount } from 'enzyme'; -import toJson from 'enzyme-to-json'; - -import { Commit } from './commit'; -import { CommitInfo } from '../../../model/commit'; - -describe('Commit component', () => { - test('renders correctly for a commit', () => { - const commitInfo: CommitInfo = { - updated: new Date(Date.UTC(2222, 10, 11)), - message: 'This is my commit message\n\nThis is the description', - author: 'author', - committer: 'committer', - id: '4ba67b8', - parents: ['9817575'], - treeId: '96440ceb55e04a99d33c1c8ee021400a680fbf74', - }; - const wrapper = mount( - - ); - - expect(toJson(wrapper)).toMatchSnapshot(); - }); -}); diff --git a/x-pack/legacy/plugins/code/public/components/commits/commit.tsx b/x-pack/legacy/plugins/code/public/components/commits/commit.tsx deleted file mode 100644 index 9326fdffe7ef8..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/commits/commit.tsx +++ /dev/null @@ -1,129 +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 React from 'react'; -import { - EuiButtonIcon, - EuiButtonEmpty, - EuiFlexGroup, - EuiLink, - EuiPanel, - EuiText, - EuiTextColor, - EuiCopy, - EuiTitle, -} from '@elastic/eui'; -import chrome from 'ui/chrome'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { CommitInfo } from '../../../model/commit'; -import { RepositoryUtils } from '../../../common/repository_utils'; -import { parseCommitMessage } from '../../../common/commit_utils'; -import { PathTypes } from '../../common/types'; - -const COMMIT_ID_LENGTH = 8; - -const RepoLink = ({ repoUri }: { repoUri: string }) => { - const repoOrg = RepositoryUtils.orgNameFromUri(repoUri); - const repoName = RepositoryUtils.repoNameFromUri(repoUri); - const repoPath = `#/${repoUri}`; - - return ( - <> - - {repoOrg} / {repoName} - - - • - - - ); -}; - -interface ActionProps { - repoUri: string; - commitId: string; -} - -const copyLabel = i18n.translate('xpack.code.commits.copyCommitAriaLabel', { - defaultMessage: 'Copy the full commit ID', -}); -const revisionLinkLabel = i18n.translate('xpack.code.commits.revisionLinkAriaLabel', { - defaultMessage: 'View the project at this commit', -}); - -const getRevisionPath = (repoUri: string, commitId: string) => { - const diffPageEnabled = chrome.getInjected('codeDiffPageEnabled'); - if (diffPageEnabled) { - return `#/${repoUri}/commit/${commitId}`; - } else { - return `#/${repoUri}/${PathTypes.tree}/${commitId}`; - } -}; - -const CommitActions = ({ commitId, repoUri }: ActionProps) => { - const revisionPath = getRevisionPath(repoUri, commitId); - - return ( -
- - {copy => } - - - {commitId} - - -
- ); -}; - -interface Props { - commit: CommitInfo; - date: string; - repoUri: string; - showRepoLink: boolean; -} - -export const Commit = (props: Props) => { - const { date, commit, repoUri, showRepoLink } = props; - const { message, committer, id } = commit; - const { summary, body } = parseCommitMessage(message); - const commitId = id.substring(0, COMMIT_ID_LENGTH); - - return ( - - -
- -

{summary}

-
- - {showRepoLink && } - - - - - -

{body}

-
-
- -
-
- ); -}; diff --git a/x-pack/legacy/plugins/code/public/components/commits/group.tsx b/x-pack/legacy/plugins/code/public/components/commits/group.tsx deleted file mode 100644 index 01c2844d38622..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/commits/group.tsx +++ /dev/null @@ -1,53 +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 React from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiText, EuiTextColor } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { CommitInfo } from '../../../model/commit'; -import { Commit } from './commit'; - -interface Props { - commits: CommitInfo[]; - date: string; - repoUri: string; -} - -export const CommitGroup = (props: Props) => { - const commitList = props.commits.map(commit => ( - - )); - - return ( -
- - -
- - - -

- - - -

-
-
- -
{commitList}
-
- ); -}; diff --git a/x-pack/legacy/plugins/code/public/components/commits/history.tsx b/x-pack/legacy/plugins/code/public/components/commits/history.tsx deleted file mode 100644 index 590e081dc2967..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/commits/history.tsx +++ /dev/null @@ -1,99 +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 _ from 'lodash'; -import moment from 'moment'; -import React from 'react'; -import { connect } from 'react-redux'; -import { i18n } from '@kbn/i18n'; -import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; - -import { CommitGroup } from './group'; -import { CommitInfo } from '../../../model/commit'; -import { RootState } from '../../reducers'; -import { hasMoreCommitsSelector, treeCommitsSelector } from '../../selectors'; -import { fetchMoreCommits } from '../../actions'; - -const CommitHistoryLoading = () => ( -
- -
-); - -const PageButtons = (props: { loading?: boolean; disabled: boolean; onClick: () => void }) => ( - - - - - - - -); - -const commitDateFormatMap: { [key: string]: string } = { - en: 'MMM Do, YYYY', - 'zh-cn': 'YYYY年MoDo', -}; - -export const CommitHistoryComponent = (props: { - commits: CommitInfo[]; - repoUri: string; - header: React.ReactNode; - loadingCommits?: boolean; - showPagination?: boolean; - hasMoreCommit?: boolean; - fetchMoreCommits: any; -}) => { - const commits = _.groupBy(props.commits, commit => moment(commit.updated).format('YYYYMMDD')); - const commitDates = Object.keys(commits).sort((a, b) => b.localeCompare(a)); // sort desc - const locale = i18n.getLocale(); - const commitDateFormat = - locale in commitDateFormatMap ? commitDateFormatMap[locale] : commitDateFormatMap.en; - const commitList = commitDates.map(cd => ( - - )); - return ( -
-
{props.header}
- {commitList} - {!props.showPagination && props.loadingCommits && } - {props.showPagination && ( - props.fetchMoreCommits(props.repoUri)} - loading={props.loadingCommits} - /> - )} -
- ); -}; - -const mapStateToProps = (state: RootState) => ({ - file: state.file.file, - commits: treeCommitsSelector(state) || [], - loadingCommits: state.revision.loadingCommits, - hasMoreCommit: hasMoreCommitsSelector(state), -}); - -const mapDispatchToProps = { - fetchMoreCommits, -}; -export const CommitHistory = connect( - mapStateToProps, - mapDispatchToProps -)(CommitHistoryComponent); diff --git a/x-pack/legacy/plugins/code/public/components/commits/index.ts b/x-pack/legacy/plugins/code/public/components/commits/index.ts deleted file mode 100644 index 868597cc5e9c4..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/commits/index.ts +++ /dev/null @@ -1,9 +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 { Commit } from './commit'; -export { CommitGroup } from './group'; -export { CommitHistory, CommitHistoryComponent } from './history'; diff --git a/x-pack/legacy/plugins/code/public/components/diff_page/accordion.tsx b/x-pack/legacy/plugins/code/public/components/diff_page/accordion.tsx deleted file mode 100644 index 2d5bf8e63a437..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/diff_page/accordion.tsx +++ /dev/null @@ -1,37 +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 React, { useState } from 'react'; -import { EuiIcon, EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; - -interface Props { - initialIsOpen: boolean; - title: React.ReactNode; - children: React.ReactNode; - className: string; -} - -export const Accordion = (props: Props) => { - const [isOpen, setOpen] = useState(props.initialIsOpen); - return ( - - { - setOpen(!isOpen); - }} - > - {props.title} - - - - - - - ); -}; diff --git a/x-pack/legacy/plugins/code/public/components/diff_page/diff.scss b/x-pack/legacy/plugins/code/public/components/diff_page/diff.scss deleted file mode 100644 index c10feae4f9dab..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/diff_page/diff.scss +++ /dev/null @@ -1,48 +0,0 @@ -.codeDiffCommitSelectorsContainer { - border-left: $euiBorderThin solid $euiColorLightShade; - padding-left: $euiSize; -} - -.codeDiffCommitMessage { - padding: $euiSizeS $euiSize; -} - -.codeDiffChangedFiles { - color: $euiColorPrimary; -} - -.codeDiffMetadata { - padding: 0 $euiSize $euiSizeS $euiSize; -} - -.codeVisualizerIcon { - margin-right: $euiSizeS; -} - -.codeDiffDeletion { - margin: 0 $euiSizeS; - background-color: $euiColorVis0; -} - -.codeDiff__panel { - padding: 0 $euiSize; -} - -.codeDiff__header { - border-bottom: $euiBorderThin solid $euiColorLightShade; - height: $euiSizeXXL; - padding: $euiSize; -} - -.codeAccordionCollapse__icon { - margin: auto $euiSize auto 0; - cursor: pointer; -} - -.codeViewFile__button { - height: $euiSizeL !important; -} - -.codeAccordion { - margin-bottom: $euiSizeM; -} diff --git a/x-pack/legacy/plugins/code/public/components/diff_page/diff.tsx b/x-pack/legacy/plugins/code/public/components/diff_page/diff.tsx deleted file mode 100644 index 98a694c2542d1..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/diff_page/diff.tsx +++ /dev/null @@ -1,177 +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 { - EuiFlexGroup, - EuiFlexItem, - EuiIcon, - EuiText, - EuiBadge, - EuiNotificationBadge, -} from '@elastic/eui'; -import React from 'react'; -import { connect } from 'react-redux'; -import { Link, RouteComponentProps, withRouter } from 'react-router-dom'; -import { CommitDiff, FileDiff } from '../../../common/git_diff'; -import { SearchScope, SearchOptions } from '../../../model'; -import { changeSearchScope } from '../../actions'; -import { RootState } from '../../reducers'; -import { SearchBar } from '../search_bar'; -import { DiffEditor } from './diff_editor'; -import { Accordion } from './accordion'; - -const COMMIT_ID_LENGTH = 16; - -interface Props extends RouteComponentProps<{ resource: string; org: string; repo: string }> { - commit: CommitDiff | null; - query: string; - onSearchScopeChanged: (s: SearchScope) => void; - repoScope: string[]; - searchOptions: SearchOptions; -} - -export enum DiffLayout { - Unified, - Split, -} - -const Difference = (props: { - fileDiff: FileDiff; - repoUri: string; - revision: string; - initialIsOpen: boolean; -}) => ( - - - - - - {props.fileDiff.additions} - - {props.fileDiff.deletions} - - - - {props.fileDiff.path} - - - -
- - - View File - - -
-
-
- } - > - - -); - -export class DiffPage extends React.Component { - public state = { - diffLayout: DiffLayout.Split, - }; - - public setLayoutUnified = () => { - this.setState({ diffLayout: DiffLayout.Unified }); - }; - - public setLayoutSplit = () => { - this.setState({ diffLayout: DiffLayout.Split }); - }; - - public render() { - const DEFAULT_OPEN_FILE_DIFF_COUNT = 1; - const { commit, match } = this.props; - const { repo, org, resource } = match.params; - const repoUri = `${resource}/${org}/${repo}`; - if (!commit) { - return null; - } - const { additions, deletions, files } = commit; - const fileCount = files.length; - const diffs = commit.files.map((file, index) => ( - - )); - return ( -
- -
- {commit.commit.message} -
- - - - - Showing - {fileCount} Changed files - with - {additions} additions and {deletions} deletions - - - - - Committed by - {commit.commit.committer} - {commit.commit.id.substr(0, COMMIT_ID_LENGTH)} - - - -
{diffs}
-
- ); - } -} - -const mapStateToProps = (state: RootState) => ({ - commit: state.commit.commit, - query: state.search.query, - repoScope: state.search.searchOptions.repoScope.map(r => r.uri), - searchOptions: state.search.searchOptions, -}); - -const mapDispatchToProps = { - onSearchScopeChanged: changeSearchScope, -}; - -export const Diff = withRouter( - connect( - mapStateToProps, - mapDispatchToProps - )(DiffPage) -); diff --git a/x-pack/legacy/plugins/code/public/components/diff_page/diff_editor.tsx b/x-pack/legacy/plugins/code/public/components/diff_page/diff_editor.tsx deleted file mode 100644 index e02d54a729192..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/diff_page/diff_editor.tsx +++ /dev/null @@ -1,68 +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 { editor } from 'monaco-editor'; -import React from 'react'; -import { MonacoDiffEditor } from '../../monaco/monaco_diff_editor'; - -interface Props { - originCode: string; - modifiedCode: string; - language: string; - renderSideBySide: boolean; -} - -export class DiffEditor extends React.Component { - lineHeight = 18; - static linesCount(s: string = '') { - let count = 0; - let position = 0; - while (position !== -1) { - count++; - position = position + 1; - position = s.indexOf('\n', position); - } - return count; - } - private diffEditor: MonacoDiffEditor | null = null; - public mountDiffEditor = (container: HTMLDivElement) => { - this.diffEditor = new MonacoDiffEditor( - container, - this.props.originCode, - this.props.modifiedCode, - this.props.language, - this.props.renderSideBySide - ); - this.diffEditor.init(); - }; - - getEditorHeight = () => { - const originalLinesCount = DiffEditor.linesCount(this.props.originCode); - const modifiedLinesCount = DiffEditor.linesCount(this.props.modifiedCode); - return Math.min(Math.max(originalLinesCount, modifiedLinesCount) * this.lineHeight, 400); - }; - - public componentDidUpdate(prevProps: Props) { - if (prevProps.renderSideBySide !== this.props.renderSideBySide) { - this.updateLayout(this.props.renderSideBySide); - } - } - - public updateLayout(renderSideBySide: boolean) { - this.diffEditor!.diffEditor!.updateOptions({ renderSideBySide } as editor.IDiffEditorOptions); - } - - public render() { - return ( -
- ); - } -} diff --git a/x-pack/legacy/plugins/code/public/components/editor/editor.tsx b/x-pack/legacy/plugins/code/public/components/editor/editor.tsx deleted file mode 100644 index b5ac5b3733881..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/editor/editor.tsx +++ /dev/null @@ -1,282 +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 { EuiFlexItem } from '@elastic/eui'; -import { editor as editorInterfaces, IDisposable } from 'monaco-editor'; -import React from 'react'; -import { connect } from 'react-redux'; -import { RouteComponentProps, withRouter } from 'react-router-dom'; -import { Hover, Position } from 'vscode-languageserver-protocol'; -import { GitBlame } from '../../../common/git_blame'; -import { closePanel, FetchFileResponse, hoverResult } from '../../actions'; -import { MainRouteParams } from '../../common/types'; -import { BlameWidget } from '../../monaco/blame/blame_widget'; -import { monaco } from '../../monaco/monaco'; -import { MonacoHelper } from '../../monaco/monaco_helper'; -import { RootState } from '../../reducers'; -import { refUrlSelector } from '../../selectors'; -import { history } from '../../utils/url'; -import { Modifier, Shortcut } from '../shortcuts'; -import { ReferencesPanel } from './references_panel'; -import { encodeRevisionString } from '../../../common/uri_util'; -import { trackCodeUiMetric, METRIC_TYPE } from '../../services/ui_metric'; -import { CodeUIUsageMetrics } from '../../../model/usage_telemetry_metrics'; - -export interface EditorActions { - closePanel(changeUrl: boolean): void; - hoverResult(hover: Hover): void; -} - -interface Props { - hidden?: boolean; - file?: FetchFileResponse; - revealPosition?: Position; - panelShowing: boolean; - isPanelLoading: boolean; - panelContents: any[]; - panelTitle: string; - hover?: Hover; - refUrl?: string; - blames: GitBlame[]; - showBlame: boolean; -} - -type IProps = Props & EditorActions & RouteComponentProps; - -export class EditorComponent extends React.Component { - static defaultProps = { - hidden: false, - }; - - public blameWidgets: any; - private container: HTMLElement | undefined; - private monaco: MonacoHelper | undefined; - private editor: editorInterfaces.IStandaloneCodeEditor | undefined; - private lineDecorations: string[] | null = null; - private gutterClickHandler: IDisposable | undefined; - - constructor(props: IProps, context: any) { - super(props, context); - } - - registerGutterClickHandler = () => { - if (!this.gutterClickHandler) { - this.gutterClickHandler = this.editor!.onMouseDown( - (e: editorInterfaces.IEditorMouseEvent) => { - // track line number click count - trackCodeUiMetric(METRIC_TYPE.COUNT, CodeUIUsageMetrics.LINE_NUMBER_CLICK_COUNT); - const { resource, org, repo, revision, path, pathType } = this.props.match.params; - const queryString = this.props.location.search; - const repoUri = `${resource}/${org}/${repo}`; - if (e.target.type === monaco.editor.MouseTargetType.GUTTER_LINE_NUMBERS) { - const url = `${repoUri}/${pathType}/${encodeRevisionString(revision)}/${path}`; - const position = e.target.position || { lineNumber: 0, column: 0 }; - history.push(`/${url}!L${position.lineNumber}:0${queryString}`); - } - this.monaco!.container.focus(); - } - ); - } - }; - - public componentDidMount(): void { - this.container = document.getElementById('mainEditor') as HTMLElement; - this.monaco = new MonacoHelper(this.container, this.props, this.props.location.search); - if (!this.props.revealPosition) { - this.monaco.clearLineSelection(); - } - const { file } = this.props; - if (file && file.content) { - const { uri, path, revision } = file.payload; - this.loadText(file.content, uri, path, file.lang!, revision).then(() => { - if (this.props.revealPosition) { - this.revealPosition(this.props.revealPosition); - } - if (this.props.showBlame) { - this.loadBlame(this.props.blames); - } - }); - } - } - - public componentDidUpdate(prevProps: IProps) { - const { file } = this.props; - if (!file) { - return; - } - const { uri, path, revision } = file.payload; - const { - resource, - org, - repo, - revision: routeRevision, - path: routePath, - } = this.props.match.params; - const prevContent = prevProps.file && prevProps.file.content; - const qs = this.props.location.search; - if (!this.props.revealPosition && this.monaco) { - this.monaco.clearLineSelection(); - } - if (prevContent !== file.content) { - this.loadText(file.content!, uri, path, file.lang!, revision).then(() => { - if (this.props.revealPosition) { - this.revealPosition(this.props.revealPosition); - } - }); - } else if ( - file.payload.uri === `${resource}/${org}/${repo}` && - file.payload.revision === routeRevision && - file.payload.path === routePath && - prevProps.revealPosition !== this.props.revealPosition - ) { - this.revealPosition(this.props.revealPosition); - } - if (this.monaco && qs !== prevProps.location.search) { - this.monaco.updateUrlQuery(qs); - } - if (this.editor) { - if (prevProps.showBlame !== this.props.showBlame && this.props.showBlame) { - this.editor.updateOptions({ lineDecorationsWidth: 316 }); - this.loadBlame(this.props.blames); - } else if (!this.props.showBlame) { - this.destroyBlameWidgets(); - this.editor.updateOptions({ lineDecorationsWidth: 16 }); - } - if (prevProps.blames !== this.props.blames && this.props.showBlame) { - this.editor.updateOptions({ lineDecorationsWidth: 316 }); - this.loadBlame(this.props.blames); - } - } - } - - public componentWillUnmount() { - if (this.gutterClickHandler) { - this.gutterClickHandler.dispose(); - } - this.monaco!.destroy(); - } - public render() { - return ( -
- - -
- -
-
-
-
- - -
-
- - - - -
-
-
-
- -
-
- - - -
-
-
-
-
-
- -
-
-
-
-
-
- - - -`; diff --git a/x-pack/legacy/plugins/code/public/components/query_bar/components/index.ts b/x-pack/legacy/plugins/code/public/components/query_bar/components/index.ts deleted file mode 100644 index 0c6b5535c34e3..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/query_bar/components/index.ts +++ /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 { QueryBar } from './query_bar'; diff --git a/x-pack/legacy/plugins/code/public/components/query_bar/components/options.tsx b/x-pack/legacy/plugins/code/public/components/query_bar/components/options.tsx deleted file mode 100644 index aa0fa04b726b2..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/query_bar/components/options.tsx +++ /dev/null @@ -1,254 +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 { - EuiButton, - EuiButtonEmpty, - EuiComboBox, - EuiFlexGroup, - EuiFlyout, - EuiFlyoutBody, - EuiFlyoutHeader, - EuiPanel, - EuiSpacer, - EuiText, - EuiTextColor, - EuiTitle, - EuiNotificationBadge, -} from '@elastic/eui'; -import { EuiIcon } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { i18n } from '@kbn/i18n'; -import { unique } from 'lodash'; -import React, { Component } from 'react'; -import { SearchOptions as ISearchOptions, Repository } from '../../../../model'; - -interface State { - isFlyoutOpen: boolean; - repoScope: Repository[]; - query: string; - defaultRepoScopeOn: boolean; -} - -interface Props { - repositorySearch: (p: { query: string }) => void; - saveSearchOptions: (searchOptions: ISearchOptions) => void; - repoSearchResults: any[]; - searchLoading: boolean; - searchOptions: ISearchOptions; - defaultRepoOptions: Repository[]; - defaultSearchScope?: Repository; -} - -export class SearchOptions extends Component { - public state: State = { - query: '', - isFlyoutOpen: false, - repoScope: this.props.searchOptions.repoScope, - defaultRepoScopeOn: this.props.searchOptions.defaultRepoScopeOn, - }; - - componentDidUpdate(prevProps: Props) { - if ( - this.props.searchOptions.defaultRepoScopeOn !== prevProps.searchOptions.defaultRepoScopeOn - ) { - this.setState({ defaultRepoScopeOn: this.props.searchOptions.defaultRepoScopeOn }); - } - } - - public applyAndClose = () => { - if (this.state.defaultRepoScopeOn && this.props.defaultSearchScope) { - this.props.saveSearchOptions({ - repoScope: unique([...this.state.repoScope, this.props.defaultSearchScope], r => r.uri), - defaultRepoScopeOn: this.state.defaultRepoScopeOn, - }); - } else { - this.props.saveSearchOptions({ - repoScope: this.state.repoScope, - defaultRepoScopeOn: this.state.defaultRepoScopeOn, - }); - } - this.setState({ isFlyoutOpen: false }); - }; - - public removeRepoScope = (r: string) => () => { - this.setState(prevState => { - const nextState: any = { - repoScope: prevState.repoScope.filter(rs => rs.uri !== r), - }; - if (this.props.defaultSearchScope && r === this.props.defaultSearchScope.uri) { - nextState.defaultRepoScopeOn = false; - } - return nextState; - }); - }; - - public render() { - let optionsFlyout; - const repoScope = - this.state.defaultRepoScopeOn && this.props.defaultSearchScope - ? unique([...this.state.repoScope, this.props.defaultSearchScope], (r: Repository) => r.uri) - : this.state.repoScope; - if (this.state.isFlyoutOpen) { - const selectedRepos = repoScope.map(r => { - return ( -
- - -
- - {r.org}/ - {r.name} - -
- -
-
- -
- ); - }); - - const repoCandidates = this.state.query - ? this.props.repoSearchResults.map(repo => ({ - label: repo.name, - })) - : this.props.defaultRepoOptions.map(repo => ({ - label: repo.name, - })); - - optionsFlyout = ( - - - - - - {repoScope.length} - - - - - - - - - -

- -

-
- - - - - - - {selectedRepos} - - - - - - -
-
- ); - } - - return ( -
-
- - - {repoScope.length} - - - - - -
- {optionsFlyout} -
- ); - } - - private onRepoSearchChange = (searchValue: string) => { - this.setState({ query: searchValue }); - if (searchValue) { - this.props.repositorySearch({ - query: searchValue, - }); - } - }; - - private onRepoChange = (repos: any) => { - this.setState(prevState => ({ - repoScope: unique( - [ - ...prevState.repoScope, - ...repos.map((r: any) => - [...this.props.repoSearchResults, ...this.props.defaultRepoOptions].find( - rs => rs.name === r.label - ) - ), - ], - (r: Repository) => r.uri - ), - })); - }; - - private toggleOptionsFlyout = () => { - this.setState({ - isFlyoutOpen: !this.state.isFlyoutOpen, - }); - }; - - private closeOptionsFlyout = () => { - this.setState({ - isFlyoutOpen: false, - repoScope: this.props.searchOptions.repoScope, - defaultRepoScopeOn: this.props.searchOptions.defaultRepoScopeOn, - }); - }; -} diff --git a/x-pack/legacy/plugins/code/public/components/query_bar/components/query_bar.test.tsx b/x-pack/legacy/plugins/code/public/components/query_bar/components/query_bar.test.tsx deleted file mode 100644 index 0d521883af87b..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/query_bar/components/query_bar.test.tsx +++ /dev/null @@ -1,169 +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 { mount } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import React from 'react'; -import { MemoryRouter } from 'react-router-dom'; -import sinon from 'sinon'; - -import { SearchScope } from '../../../../model'; -import { AutocompleteSuggestionType } from '../suggestions'; -import props from './__fixtures__/props.json'; -import { CodeQueryBar } from './query_bar'; - -// Injest a mock random function to fixiate the output for generating component id. -const mockMath = Object.create(global.Math); -mockMath.random = () => 0.5; -global.Math = mockMath; - -test('render correctly with empty query string', () => { - const emptyFn = () => { - return; - }; - const queryBarComp = mount( - - ); - expect(toJson(queryBarComp)).toMatchSnapshot(); -}); - -test('render correctly with input query string changed', done => { - const emptyFn = () => { - return; - }; - - const fileSuggestionsSpy = sinon.fake.returns( - Promise.resolve(props[AutocompleteSuggestionType.FILE]) - ); - const symbolSuggestionsSpy = sinon.fake.returns( - Promise.resolve(props[AutocompleteSuggestionType.SYMBOL]) - ); - const repoSuggestionsSpy = sinon.fake.returns( - Promise.resolve(props[AutocompleteSuggestionType.REPOSITORY]) - ); - - const mockFileSuggestionsProvider = { getSuggestions: fileSuggestionsSpy }; - const mockSymbolSuggestionsProvider = { getSuggestions: symbolSuggestionsSpy }; - const mockRepositorySuggestionsProvider = { getSuggestions: repoSuggestionsSpy }; - const submitSpy = sinon.spy(); - - const queryBarComp = mount( - - - - ); - - // Input 'mockquery' in the query bar. - queryBarComp - .find('input[type="text"]') - .at(0) - .simulate('change', { target: { value: 'mockquery' } }); - - // Wait for 101ms to make sure the getSuggestions has been triggered. - setTimeout(() => { - expect(toJson(queryBarComp)).toMatchSnapshot(); - expect(fileSuggestionsSpy.calledOnce).toBeTruthy(); - expect(symbolSuggestionsSpy.calledOnce).toBeTruthy(); - expect(repoSuggestionsSpy.calledOnce).toBeTruthy(); - - // Hit enter - queryBarComp - .find('input[type="text"]') - .at(0) - .simulate('keyDown', { keyCode: 13, key: 'Enter', metaKey: true }); - expect(submitSpy.calledOnce).toBeTruthy(); - - done(); - }, 1000); -}); - -test('invokes onSearchScopeChanged and gets new suggestions when the scope is changed', async () => { - const noOp = () => {}; - const wait = (ms = 0) => new Promise(resolve => setTimeout(resolve, ms)); - - const fileSuggestionsSpy = jest.fn(() => Promise.resolve(props[AutocompleteSuggestionType.FILE])); - const symbolSuggestionsSpy = jest.fn(() => - Promise.resolve(props[AutocompleteSuggestionType.SYMBOL]) - ); - const repoSuggestionsSpy = jest.fn(() => - Promise.resolve(props[AutocompleteSuggestionType.REPOSITORY]) - ); - const suggestionsProviderSpies = [ - { getSuggestions: fileSuggestionsSpy }, - { getSuggestions: symbolSuggestionsSpy }, - { getSuggestions: repoSuggestionsSpy }, - ]; - const onSearchScopeChangedSpy = jest.fn(); - - const wrapper = mount( - - ); - - wrapper - .find('ScopeSelector') - .find('button') - .simulate('click'); - wrapper - .find('ScopeSelector') - .find('EuiContextMenuItem') - .at(1) - .simulate('click'); - await wait(200); // wait for getSuggestions' debounce - - expect(onSearchScopeChangedSpy).toHaveBeenCalledWith(AutocompleteSuggestionType.SYMBOL); - expect(symbolSuggestionsSpy).toHaveBeenCalledWith('testQuery', SearchScope.DEFAULT, []); -}); diff --git a/x-pack/legacy/plugins/code/public/components/query_bar/components/query_bar.tsx b/x-pack/legacy/plugins/code/public/components/query_bar/components/query_bar.tsx deleted file mode 100644 index 5bb08f56af42b..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/query_bar/components/query_bar.tsx +++ /dev/null @@ -1,532 +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 { debounce, isEqual } from 'lodash'; -import React, { Component } from 'react'; - -import { EuiFieldText, EuiFlexGroup, EuiFlexItem, EuiOutsideClickDetector } from '@elastic/eui'; -import { connect } from 'react-redux'; -import { saveSearchOptions, searchReposForScope, suggestionSearch } from '../../../actions'; -import { matchPairs } from '../lib/match_pairs'; -import { SuggestionsComponent } from './typeahead/suggestions_component'; - -import { SearchOptions as ISearchOptions, SearchScope, Repository } from '../../../../model'; -import { SearchScopePlaceholderText } from '../../../common/types'; -import { RootState } from '../../../reducers'; -import { - AutocompleteSuggestion, - AutocompleteSuggestionGroup, - SuggestionsProvider, -} from '../suggestions'; -import { SearchOptions } from './options'; -import { ScopeSelector } from './scope_selector'; - -const KEY_CODES = { - LEFT: 37, - UP: 38, - RIGHT: 39, - DOWN: 40, - ENTER: 13, - ESC: 27, - TAB: 9, - HOME: 36, - END: 35, -}; - -interface Props { - query: string; - onSubmit: (query: string) => void; - onSelect: (item: AutocompleteSuggestion, query: string) => void; - onSuggestionQuerySubmitted?: (query: string) => void; - disableAutoFocus?: boolean; - appName: string; - suggestionProviders: SuggestionsProvider[]; - repositorySearch: (p: { query: string }) => void; - saveSearchOptions: (searchOptions: ISearchOptions) => void; - enableSubmitWhenOptionsChanged: boolean; - onSearchScopeChanged: (s: SearchScope) => void; - repoSearchResults: any[]; - searchLoading: boolean; - searchScope: SearchScope; - searchOptions: ISearchOptions; - defaultRepoOptions: Repository[]; - currentRepository?: Repository; -} - -interface State { - query: string; - inputIsPristine: boolean; - isSuggestionsVisible: boolean; - groupIndex: number | null; - itemIndex: number | null; - suggestionGroups: AutocompleteSuggestionGroup[]; - showOptions: boolean; - currentProps?: Props; -} - -export class CodeQueryBar extends Component { - public static getDerivedStateFromProps(nextProps: Props, prevState: State) { - if (isEqual(prevState.currentProps, nextProps)) { - return null; - } - - const nextState: any = { - currentProps: nextProps, - }; - if (nextProps.query !== prevState.query) { - nextState.query = nextProps.query; - } - return nextState; - } - - /* - Keep the "draft" value in local state until the user actually submits the query. There are a couple advantages: - - 1. Each app doesn't have to maintain its own "draft" value if it wants to put off updating the query in app state - until the user manually submits their changes. Most apps have watches on the query value in app state so we don't - want to trigger those on every keypress. Also, some apps (e.g. dashboard) already juggle multiple query values, - each with slightly different semantics and I'd rather not add yet another variable to the mix. - - 2. Changes to the local component state won't trigger an Angular digest cycle. Triggering digest cycles on every - keypress has been a major source of performance issues for us in previous implementations of the query bar. - See https://github.com/elastic/kibana/issues/14086 - */ - public state: State = { - query: this.props.query, - inputIsPristine: true, - isSuggestionsVisible: false, - groupIndex: null, - itemIndex: null, - suggestionGroups: [], - showOptions: false, - }; - - public updateSuggestions = debounce(async () => { - const suggestionGroups = (await this.getSuggestions()) || []; - if (!this.componentIsUnmounting) { - this.setState({ suggestionGroups }); - } - }, 100); - - public inputRef: HTMLInputElement | null = null; - - public optionFlyout: any | null = null; - - private componentIsUnmounting = false; - - public isDirty = () => { - return this.state.query !== this.props.query; - }; - - public loadMore = () => { - // TODO(mengwei): Add action for load more. - }; - - public incrementIndex = (currGroupIndex: number, currItemIndex: number) => { - let nextItemIndex = currItemIndex + 1; - - if (currGroupIndex === null) { - currGroupIndex = 0; - } - let nextGroupIndex = currGroupIndex; - - const group: AutocompleteSuggestionGroup = this.state.suggestionGroups[currGroupIndex]; - if (currItemIndex === null || nextItemIndex >= group.suggestions.length) { - nextItemIndex = 0; - nextGroupIndex = currGroupIndex + 1; - if (nextGroupIndex >= this.state.suggestionGroups.length) { - nextGroupIndex = 0; - } - } - - this.setState({ - groupIndex: nextGroupIndex, - itemIndex: nextItemIndex, - }); - }; - - public decrementIndex = (currGroupIndex: number, currItemIndex: number) => { - let prevItemIndex = currItemIndex - 1; - - if (currGroupIndex === null) { - currGroupIndex = this.state.suggestionGroups.length - 1; - } - let prevGroupIndex = currGroupIndex; - - if (currItemIndex === null || prevItemIndex < 0) { - prevGroupIndex = currGroupIndex - 1; - if (prevGroupIndex < 0) { - prevGroupIndex = this.state.suggestionGroups.length - 1; - } - const group: AutocompleteSuggestionGroup = this.state.suggestionGroups[prevGroupIndex]; - prevItemIndex = group.suggestions.length - 1; - } - - this.setState({ - groupIndex: prevGroupIndex, - itemIndex: prevItemIndex, - }); - }; - - public getSuggestions = async () => { - if (!this.inputRef) { - return; - } - - const { query } = this.state; - if (query.length === 0) { - return []; - } - - if (!this.props.suggestionProviders || this.props.suggestionProviders.length === 0) { - return []; - } - - const { selectionStart, selectionEnd } = this.inputRef; - if (selectionStart === null || selectionEnd === null) { - return; - } - - if (this.props.onSuggestionQuerySubmitted) { - this.props.onSuggestionQuerySubmitted(query); - } - - const res = await Promise.all( - this.props.suggestionProviders.map((provider: SuggestionsProvider) => { - // Merge the default repository scope if necessary. - const repoScopes = this.props.searchOptions.repoScope.map(repo => repo.uri); - if ( - this.props.searchOptions.defaultRepoScopeOn && - this.props.searchOptions.defaultRepoScope - ) { - repoScopes.push(this.props.searchOptions.defaultRepoScope.uri); - } - return provider.getSuggestions(query, this.props.searchScope, repoScopes); - }) - ); - - return res.filter((group: AutocompleteSuggestionGroup) => group.suggestions.length > 0); - }; - - public selectSuggestion = (item: AutocompleteSuggestion) => { - if (!this.inputRef) { - return; - } - - const { selectionStart, selectionEnd } = this.inputRef; - if (selectionStart === null || selectionEnd === null) { - return; - } - this.setState( - { - query: this.state.query, - groupIndex: null, - itemIndex: null, - isSuggestionsVisible: false, - }, - () => { - if (item) { - this.props.onSelect(item, this.state.query); - } - } - ); - }; - - public onOutsideClick = () => { - this.setState({ isSuggestionsVisible: false, groupIndex: null, itemIndex: null }); - }; - - public onClickInput = (event: React.MouseEvent) => { - if (event.target instanceof HTMLInputElement) { - this.onInputChange(event.target.value); - } - }; - - public onClickSubmitButton = (event: React.MouseEvent) => { - this.onSubmit(() => event.preventDefault()); - }; - - public onClickSuggestion = (suggestion: AutocompleteSuggestion) => { - if (!this.inputRef) { - return; - } - this.selectSuggestion(suggestion); - this.inputRef.focus(); - }; - - public onMouseEnterSuggestion = (groupIndex: number, itemIndex: number) => { - this.setState({ groupIndex, itemIndex }); - }; - - public onInputChange = (value: string) => { - const hasValue = Boolean(value.trim()); - - this.setState({ - query: value, - inputIsPristine: false, - isSuggestionsVisible: hasValue, - groupIndex: null, - itemIndex: null, - }); - }; - - public onChange = (event: React.ChangeEvent) => { - this.updateSuggestions(); - this.onInputChange(event.target.value); - }; - - public onScopeChange = (scope: SearchScope) => { - const { onSearchScopeChanged } = this.props; - - if (onSearchScopeChanged) { - onSearchScopeChanged(scope); - } - - this.updateSuggestions(); - }; - - public onKeyUp = (event: React.KeyboardEvent) => { - if ([KEY_CODES.LEFT, KEY_CODES.RIGHT, KEY_CODES.HOME, KEY_CODES.END].includes(event.keyCode)) { - this.setState({ isSuggestionsVisible: true }); - if (event.target instanceof HTMLInputElement) { - this.onInputChange(event.target.value); - } - } - }; - - public onKeyDown = (event: React.KeyboardEvent) => { - if (event.target instanceof HTMLInputElement) { - const { isSuggestionsVisible, groupIndex, itemIndex } = this.state; - const preventDefault = event.preventDefault.bind(event); - const { target, key, metaKey } = event; - const { value, selectionStart, selectionEnd } = target; - const updateQuery = (query: string, newSelectionStart: number, newSelectionEnd: number) => { - this.setState( - { - query, - }, - () => { - target.setSelectionRange(newSelectionStart, newSelectionEnd); - } - ); - }; - - switch (event.keyCode) { - case KEY_CODES.DOWN: - event.preventDefault(); - if (isSuggestionsVisible && groupIndex !== null && itemIndex !== null) { - this.incrementIndex(groupIndex, itemIndex); - } else { - this.setState({ isSuggestionsVisible: true, groupIndex: 0, itemIndex: 0 }); - } - break; - case KEY_CODES.UP: - event.preventDefault(); - if (isSuggestionsVisible && groupIndex !== null && itemIndex !== null) { - this.decrementIndex(groupIndex, itemIndex); - } else { - const lastGroupIndex = this.state.suggestionGroups.length - 1; - const group: AutocompleteSuggestionGroup = this.state.suggestionGroups[lastGroupIndex]; - if (group !== null) { - const lastItemIndex = group.suggestions.length - 1; - this.setState({ - isSuggestionsVisible: true, - groupIndex: lastGroupIndex, - itemIndex: lastItemIndex, - }); - } - } - break; - case KEY_CODES.ENTER: - event.preventDefault(); - if ( - isSuggestionsVisible && - groupIndex !== null && - itemIndex !== null && - this.state.suggestionGroups[groupIndex] - ) { - const group: AutocompleteSuggestionGroup = this.state.suggestionGroups[groupIndex]; - this.selectSuggestion(group.suggestions[itemIndex]); - } else { - this.onSubmit(() => event.preventDefault()); - } - break; - case KEY_CODES.ESC: - event.preventDefault(); - this.setState({ isSuggestionsVisible: false, groupIndex: null, itemIndex: null }); - break; - case KEY_CODES.TAB: - this.setState({ isSuggestionsVisible: false, groupIndex: null, itemIndex: null }); - break; - default: - if (selectionStart !== null && selectionEnd !== null) { - matchPairs({ - value, - selectionStart, - selectionEnd, - key, - metaKey, - updateQuery, - preventDefault, - }); - } - - break; - } - } - }; - - public onSubmit = (preventDefault?: () => void) => { - if (preventDefault) { - preventDefault(); - } - - this.props.onSubmit(this.state.query); - this.setState({ isSuggestionsVisible: false }); - }; - - public componentDidMount() { - this.updateSuggestions(); - } - - public componentDidUpdate(prevProps: Props) { - if (prevProps.query !== this.props.query) { - this.updateSuggestions(); - } - - // When search options (e.g. repository scopes) change, - // submit the search query again to refresh the search result. - if ( - this.props.enableSubmitWhenOptionsChanged && - !_.isEqual(prevProps.searchOptions, this.props.searchOptions) - ) { - this.onSubmit(); - } - } - - public componentWillUnmount() { - this.updateSuggestions.cancel(); - this.componentIsUnmounting = true; - } - - public focusInput() { - if (this.inputRef) { - this.inputRef.focus(); - } - } - - public toggleOptionsFlyout() { - if (this.optionFlyout) { - this.optionFlyout.toggleOptionsFlyout(); - } - } - - public render() { - const inputRef = (node: HTMLInputElement | null) => { - if (node) { - this.inputRef = node; - } - }; - const activeDescendant = this.state.isSuggestionsVisible - ? `suggestion-${this.state.groupIndex}-${this.state.itemIndex}` - : ''; - return ( - - - - - - -
-
-
-
- - (this.optionFlyout = element)} - /> -
-
-
- - -
-
-
-
- ); - } -} - -const mapStateToProps = (state: RootState) => ({ - repoSearchResults: state.search.scopeSearchResults.repositories, - searchLoading: state.search.isScopeSearchLoading, - searchScope: state.search.scope, - searchOptions: state.search.searchOptions, - defaultRepoOptions: state.repositoryManagement.repositories.slice(0, 5), - currentRepository: state.repository.repository, -}); - -const mapDispatchToProps = { - repositorySearch: searchReposForScope, - saveSearchOptions, - onSuggestionQuerySubmitted: suggestionSearch, -}; - -export const QueryBar = connect( - mapStateToProps, - mapDispatchToProps, - null, - { withRef: true } -)(CodeQueryBar); diff --git a/x-pack/legacy/plugins/code/public/components/query_bar/components/scope_selector.tsx b/x-pack/legacy/plugins/code/public/components/query_bar/components/scope_selector.tsx deleted file mode 100644 index 26114ced284f5..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/query_bar/components/scope_selector.tsx +++ /dev/null @@ -1,65 +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 { EuiIcon, EuiSuperSelect, EuiText } from '@elastic/eui'; -import React, { Component } from 'react'; - -import { SearchScope } from '../../../../model'; -import { SearchScopeText } from '../../../common/types'; - -interface Props { - scope: SearchScope; - onScopeChanged: (s: SearchScope) => void; -} - -export class ScopeSelector extends Component { - public scopeOptions = [ - { - value: SearchScope.DEFAULT, - inputDisplay: ( -
- - {SearchScopeText[SearchScope.DEFAULT]} - -
- ), - }, - { - value: SearchScope.SYMBOL, - inputDisplay: ( - - {SearchScopeText[SearchScope.SYMBOL]} - - ), - }, - { - value: SearchScope.REPOSITORY, - inputDisplay: ( - - {SearchScopeText[SearchScope.REPOSITORY]} - - ), - }, - { - value: SearchScope.FILE, - inputDisplay: ( - - {SearchScopeText[SearchScope.FILE]} - - ), - }, - ]; - - public render() { - return ( - - ); - } -} diff --git a/x-pack/legacy/plugins/code/public/components/query_bar/components/typeahead/__snapshots__/suggestion_component.test.tsx.snap b/x-pack/legacy/plugins/code/public/components/query_bar/components/typeahead/__snapshots__/suggestion_component.test.tsx.snap deleted file mode 100644 index 711476e92b4c6..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/query_bar/components/typeahead/__snapshots__/suggestion_component.test.tsx.snap +++ /dev/null @@ -1,196 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`render file item 1`] = ` - -
-
-
-
- - src/foo/ - - bar - - .java - -
-
- This is a file -
-
-
-
-
-`; - -exports[`render repository item 1`] = ` - -
-
-
-
- - elastic/ - - kibana - - -
-
-
-
-
- -`; - -exports[`render symbol item 1`] = ` - src/foo/bar.java", - "end": 10, - "selectUrl": "http://github.com/elastic/elasticsearch/src/foo/bar.java", - "start": 1, - "text": "java.lang.String", - "tokenType": "tokenClass", - } - } -> -
-
-
- -
- - - - - -
-
-
-
-
- - java.lang. - - String - - -
-
- elastic/elasticsearch > src/foo/bar.java -
-
-
-
-
-`; diff --git a/x-pack/legacy/plugins/code/public/components/query_bar/components/typeahead/__snapshots__/suggestions_component.test.tsx.snap b/x-pack/legacy/plugins/code/public/components/query_bar/components/typeahead/__snapshots__/suggestions_component.test.tsx.snap deleted file mode 100644 index 323e169c06488..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/query_bar/components/typeahead/__snapshots__/suggestions_component.test.tsx.snap +++ /dev/null @@ -1,654 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`render empty suggestions component 1`] = ` - -`; - -exports[`render full suggestions component 1`] = ` - - - src/foo/bar.java", - "end": 10, - "selectUrl": "http://github.com/elastic/elasticsearch/src/foo/bar.java", - "start": 1, - "text": "java.lang.String", - "tokenType": "tokenClass", - }, - ], - "total": 1, - "type": "symbol", - }, - Object { - "hasMore": false, - "suggestions": Array [ - Object { - "description": "This is a file", - "end": 10, - "selectUrl": "http://github.com/elastic/elasticsearch/src/foo/bar.java", - "start": 1, - "text": "src/foo/bar.java", - "tokenType": "", - }, - ], - "total": 1, - "type": "file", - }, - Object { - "hasMore": true, - "suggestions": Array [ - Object { - "description": "", - "end": 10, - "selectUrl": "http://github.com/elastic/kibana", - "start": 1, - "text": "elastic/kibana", - "tokenType": "", - }, - Object { - "description": "", - "end": 10, - "selectUrl": "http://github.com/elastic/elasticsearch", - "start": 1, - "text": "elastic/elasticsearch", - "tokenType": "", - }, - ], - "total": 2, - "type": "repository", - }, - ] - } - > -
-
-
-
- -
- -
- -
- - - - - -
-
- -
- Symbols -
-
-
-
-
- - - 1 Result - - -
-
-
- src/foo/bar.java", - "end": 10, - "selectUrl": "http://github.com/elastic/elasticsearch/src/foo/bar.java", - "start": 1, - "text": "java.lang.String", - "tokenType": "tokenClass", - } - } - > -
-
-
- -
- - - - - -
-
-
-
-
- - java.lang. - - String - - -
-
- elastic/elasticsearch > src/foo/bar.java -
-
-
-
-
-
-
- -
- -
- -
- - - - - -
-
- -
- Files -
-
-
-
-
- - - 1 Result - - -
-
-
- -
-
-
-
- src/foo/bar.java -
-
- This is a file -
-
-
-
-
-
-
- -
- -
- -
- - - - - -
-
- -
- Repos -
-
-
-
-
- - - 2 Results - - -
-
-
- -
-
-
-
- elastic/kibana -
-
-
-
-
- - -
-
-
-
- elastic/elasticsearch -
-
-
-
-
- - -
- - -
- - - Press ⮐ Return for Full Text Search - - -
-
- -
-
-
- - - -`; diff --git a/x-pack/legacy/plugins/code/public/components/query_bar/components/typeahead/suggestion_component.test.tsx b/x-pack/legacy/plugins/code/public/components/query_bar/components/typeahead/suggestion_component.test.tsx deleted file mode 100644 index d0e9a24849503..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/query_bar/components/typeahead/suggestion_component.test.tsx +++ /dev/null @@ -1,70 +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 { mount } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import React from 'react'; -import sinon from 'sinon'; - -import props from '../__fixtures__/props.json'; -import { SuggestionComponent } from './suggestion_component'; - -test('render file item', () => { - const emptyFn = () => { - return; - }; - const suggestionItem = mount( - - ); - expect(toJson(suggestionItem)).toMatchSnapshot(); -}); - -test('render symbol item', () => { - const emptyFn = () => { - return; - }; - const suggestionItem = mount( - - ); - expect(toJson(suggestionItem)).toMatchSnapshot(); -}); - -test('render repository item', () => { - const emptyFn = () => { - return; - }; - const suggestionItem = mount( - - ); - expect(toJson(suggestionItem)).toMatchSnapshot(); -}); diff --git a/x-pack/legacy/plugins/code/public/components/query_bar/components/typeahead/suggestion_component.tsx b/x-pack/legacy/plugins/code/public/components/query_bar/components/typeahead/suggestion_component.tsx deleted file mode 100644 index 5a30dda32ff89..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/query_bar/components/typeahead/suggestion_component.tsx +++ /dev/null @@ -1,83 +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 { EuiToken, IconType } from '@elastic/eui'; -import React, { SFC } from 'react'; -import { AutocompleteSuggestion } from '../..'; - -interface Props { - query: string; - onClick: (suggestion: AutocompleteSuggestion) => void; - onMouseEnter: () => void; - selected: boolean; - suggestion: AutocompleteSuggestion; - innerRef: (node: HTMLDivElement) => void; - ariaId: string; -} - -export const SuggestionComponent: SFC = props => { - const click = () => props.onClick(props.suggestion); - - // An util function to help highlight the substring which matches the query. - const renderMatchingText = (text: string) => { - if (!text) { - return text; - } - // Match the text with query in case sensitive mode first. - let index = text.indexOf(props.query); - if (index < 0) { - // Fall back with case insensitive mode first. - index = text.toLowerCase().indexOf(props.query.toLowerCase()); - } - if (index >= 0) { - const prefix = text.substring(0, index); - const highlight = text.substring(index, index + props.query.length); - const surfix = text.substring(index + props.query.length); - return ( - - {prefix} - {highlight} - {surfix} - - ); - } else { - return text; - } - }; - - const icon = props.suggestion.tokenType ? ( -
- -
- ) : null; - - return ( - // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/interactive-supports-focus -
-
- {icon} -
-
- {renderMatchingText(props.suggestion.text)} -
-
{props.suggestion.description}
-
-
-
- ); -}; diff --git a/x-pack/legacy/plugins/code/public/components/query_bar/components/typeahead/suggestions_component.test.tsx b/x-pack/legacy/plugins/code/public/components/query_bar/components/typeahead/suggestions_component.test.tsx deleted file mode 100644 index 04de9a244f0cb..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/query_bar/components/typeahead/suggestions_component.test.tsx +++ /dev/null @@ -1,53 +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 { mount } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import React from 'react'; -import { MemoryRouter } from 'react-router-dom'; - -import props from '../__fixtures__/props.json'; -import { SuggestionsComponent } from './suggestions_component'; - -test('render empty suggestions component', () => { - const emptyFn = () => { - return; - }; - const suggestionItem = mount( - - ); - expect(toJson(suggestionItem)).toMatchSnapshot(); -}); - -test('render full suggestions component', () => { - const emptyFn = () => { - return; - }; - const suggestionItem = mount( - - - - ); - expect(toJson(suggestionItem)).toMatchSnapshot(); -}); diff --git a/x-pack/legacy/plugins/code/public/components/query_bar/components/typeahead/suggestions_component.tsx b/x-pack/legacy/plugins/code/public/components/query_bar/components/typeahead/suggestions_component.tsx deleted file mode 100644 index eed549b165464..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/query_bar/components/typeahead/suggestions_component.tsx +++ /dev/null @@ -1,221 +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 { EuiFlexGroup, EuiText, EuiToken, IconType } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { i18n } from '@kbn/i18n'; - -import { isEmpty } from 'lodash'; -import React, { Component } from 'react'; -import { Link } from 'react-router-dom'; -import url from 'url'; - -import { - AutocompleteSuggestion, - AutocompleteSuggestionGroup, - AutocompleteSuggestionType, -} from '../..'; -import { SuggestionComponent } from './suggestion_component'; - -interface Props { - query: string; - groupIndex: number | null; - itemIndex: number | null; - onClick: (suggestion: AutocompleteSuggestion) => void; - onMouseEnter: (groupIndex: number, itemIndex: number) => void; - show: boolean; - suggestionGroups: AutocompleteSuggestionGroup[]; - loadMore: () => void; -} - -export class SuggestionsComponent extends Component { - private childNodes: HTMLDivElement[] = []; - private parentNode: HTMLDivElement | null = null; - - private viewMoreUrl() { - return url.format({ - pathname: '/search', - query: { - q: this.props.query, - }, - }); - } - - public render() { - if (!this.props.show || isEmpty(this.props.suggestionGroups)) { - return null; - } - - return ( -
-
-
- {this.renderSuggestionGroups()} - -
- -
- -
-
-
- ); - } - - public componentDidUpdate(prevProps: Props) { - if ( - prevProps.groupIndex !== this.props.groupIndex || - prevProps.itemIndex !== this.props.itemIndex - ) { - this.scrollIntoView(); - } - } - - private renderSuggestionGroups() { - return this.props.suggestionGroups - .filter((group: AutocompleteSuggestionGroup) => group.suggestions.length > 0) - .map((group: AutocompleteSuggestionGroup, groupIndex: number) => { - const { suggestions, total, type, hasMore } = group; - const suggestionComps = suggestions.map( - (suggestion: AutocompleteSuggestion, itemIndex: number) => { - const innerRef = (node: any) => (this.childNodes[itemIndex] = node); - const mouseEnter = () => this.props.onMouseEnter(groupIndex, itemIndex); - const isSelected = - groupIndex === this.props.groupIndex && itemIndex === this.props.itemIndex; - return ( - - ); - } - ); - - const groupHeader = ( - - - - - {this.getGroupTitle(group.type)} - - -
- -
-
- ); - - const viewMore = ( -
- - {' '} - - -
- ); - - return ( -
(this.parentNode = node)} - onScroll={this.handleScroll} - key={`${type}-suggestions`} - > - {groupHeader} - {suggestionComps} - {hasMore ? viewMore : null} -
- ); - }); - } - - private getGroupTokenType(type: AutocompleteSuggestionType): string { - switch (type) { - case AutocompleteSuggestionType.FILE: - return 'tokenFile'; - case AutocompleteSuggestionType.REPOSITORY: - return 'tokenRepo'; - case AutocompleteSuggestionType.SYMBOL: - return 'tokenSymbol'; - } - } - - private getGroupTitle(type: AutocompleteSuggestionType): string { - switch (type) { - case AutocompleteSuggestionType.FILE: - return i18n.translate('xpack.code.searchBar.fileGroupTitle', { - defaultMessage: 'Files', - }); - case AutocompleteSuggestionType.REPOSITORY: - return i18n.translate('xpack.code.searchBar.repositorylGroupTitle', { - defaultMessage: 'Repos', - }); - case AutocompleteSuggestionType.SYMBOL: - return i18n.translate('xpack.code.searchBar.symbolGroupTitle', { - defaultMessage: 'Symbols', - }); - } - } - - private scrollIntoView = () => { - if (this.props.groupIndex === null || this.props.itemIndex === null) { - return; - } - const parent = this.parentNode; - const child = this.childNodes[this.props.itemIndex]; - - if (this.props.groupIndex == null || this.props.itemIndex === null || !parent || !child) { - return; - } - - const scrollTop = Math.max( - Math.min(parent.scrollTop, child.offsetTop), - child.offsetTop + child.offsetHeight - parent.offsetHeight - ); - - parent.scrollTop = scrollTop; - }; - - private handleScroll = () => { - if (!this.props.loadMore || !this.parentNode) { - return; - } - - const position = this.parentNode.scrollTop + this.parentNode.offsetHeight; - const height = this.parentNode.scrollHeight; - const remaining = height - position; - const margin = 50; - - if (!height || !position) { - return; - } - if (remaining <= margin) { - this.props.loadMore(); - } - }; -} diff --git a/x-pack/legacy/plugins/code/public/components/query_bar/index.ts b/x-pack/legacy/plugins/code/public/components/query_bar/index.ts deleted file mode 100644 index 8cd9383123677..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/query_bar/index.ts +++ /dev/null @@ -1,17 +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. - */ - -/* - * This QueryBar component is forked from the QueryBar implemented in kibana/ui/public/query_bar - * with simplifications to fulfill Code's feature request. - * - * The styles has been migrated to styled-components instead of css for any new components brought - * by Code. For shared components/styles, you can find the classes in the scss files in - * kibana/ui/public/query_bar - */ - -export * from './components'; -export * from './suggestions'; diff --git a/x-pack/legacy/plugins/code/public/components/query_bar/lib/match_pairs.ts b/x-pack/legacy/plugins/code/public/components/query_bar/lib/match_pairs.ts deleted file mode 100644 index 8919f97dd5a28..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/query_bar/lib/match_pairs.ts +++ /dev/null @@ -1,133 +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. - */ - -/** - * This helper automatically handles matching pairs. - * Specifically, it does the following: - * - * 1. If the key is a closer, and the character in front of the cursor is the - * same, simply move the cursor forward. - * 2. If the key is an opener, insert the opener at the beginning of the - * selection, and the closer at the end of the selection, and move the - * selection forward. - * 3. If the backspace is hit, and the characters before and after the cursor - * are a pair, remove both characters and move the cursor backward. - */ - -const pairs = ['()', '[]', '{}', `''`, '""']; -const openers = pairs.map(pair => pair[0]); -const closers = pairs.map(pair => pair[1]); - -interface MatchPairsOptions { - value: string; - selectionStart: number; - selectionEnd: number; - key: string; - metaKey: boolean; - updateQuery: (query: string, selectionStart: number, selectionEnd: number) => void; - preventDefault: () => void; -} - -export function matchPairs({ - value, - selectionStart, - selectionEnd, - key, - metaKey, - updateQuery, - preventDefault, -}: MatchPairsOptions) { - if (shouldMoveCursorForward(key, value, selectionStart, selectionEnd)) { - preventDefault(); - updateQuery(value, selectionStart + 1, selectionEnd + 1); - } else if (shouldInsertMatchingCloser(key, value, selectionStart, selectionEnd)) { - preventDefault(); - const newValue = - value.substr(0, selectionStart) + - key + - value.substring(selectionStart, selectionEnd) + - closers[openers.indexOf(key)] + - value.substr(selectionEnd); - updateQuery(newValue, selectionStart + 1, selectionEnd + 1); - } else if (shouldRemovePair(key, metaKey, value, selectionStart, selectionEnd)) { - preventDefault(); - const newValue = value.substr(0, selectionEnd - 1) + value.substr(selectionEnd + 1); - updateQuery(newValue, selectionStart - 1, selectionEnd - 1); - } -} - -function shouldMoveCursorForward( - key: string, - value: string, - selectionStart: number, - selectionEnd: number -) { - if (!closers.includes(key)) { - return false; - } - - // Never move selection forward for multi-character selections - if (selectionStart !== selectionEnd) { - return false; - } - - // Move selection forward if the key is the same as the closer in front of the selection - return value.charAt(selectionEnd) === key; -} - -function shouldInsertMatchingCloser( - key: string, - value: string, - selectionStart: number, - selectionEnd: number -) { - if (!openers.includes(key)) { - return false; - } - - // Always insert for multi-character selections - if (selectionStart !== selectionEnd) { - return true; - } - - const precedingCharacter = value.charAt(selectionStart - 1); - const followingCharacter = value.charAt(selectionStart + 1); - - // Don't insert if the preceding character is a backslash - if (precedingCharacter === '\\') { - return false; - } - - // Don't insert if it's a quote and the either of the preceding/following characters is alphanumeric - return !( - ['"', `'`].includes(key) && - (isAlphanumeric(precedingCharacter) || isAlphanumeric(followingCharacter)) - ); -} - -function shouldRemovePair( - key: string, - metaKey: boolean, - value: string, - selectionStart: number, - selectionEnd: number -) { - if (key !== 'Backspace' || metaKey) { - return false; - } - - // Never remove for multi-character selections - if (selectionStart !== selectionEnd) { - return false; - } - - // Remove if the preceding/following characters are a pair - return pairs.includes(value.substr(selectionEnd - 1, 2)); -} - -function isAlphanumeric(value = '') { - return value.match(/[a-zA-Z0-9_]/); -} diff --git a/x-pack/legacy/plugins/code/public/components/query_bar/suggestions/file_suggestions_provider.ts b/x-pack/legacy/plugins/code/public/components/query_bar/suggestions/file_suggestions_provider.ts deleted file mode 100644 index d03bd6cdd1863..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/query_bar/suggestions/file_suggestions_provider.ts +++ /dev/null @@ -1,60 +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 { npStart } from 'ui/new_platform'; - -import { - AbstractSuggestionsProvider, - AutocompleteSuggestionGroup, - AutocompleteSuggestionType, -} from '.'; -import { toRepoNameWithOrg } from '../../../../common/uri_util'; -import { SearchResultItem, SearchScope } from '../../../../model'; - -export class FileSuggestionsProvider extends AbstractSuggestionsProvider { - protected matchSearchScope(scope: SearchScope): boolean { - return scope === SearchScope.DEFAULT || scope === SearchScope.FILE; - } - - protected async fetchSuggestions( - query: string, - repoScope?: string[] - ): Promise { - try { - const queryParams: { q: string; repoScope?: string[] } = { q: query, repoScope }; - // actually, query accepts string[] as value's type - // @ts-ignore - const res = await npStart.core.http.get(`/api/code/suggestions/doc`, { - query: queryParams, - }); - const suggestions = Array.from(res.results as SearchResultItem[]) - .slice(0, this.MAX_SUGGESTIONS_PER_GROUP) - .map((doc: SearchResultItem) => { - return { - description: toRepoNameWithOrg(doc.uri), - end: 10, - start: 1, - text: doc.filePath, - tokenType: '', - selectUrl: `/${doc.uri}/blob/HEAD/${doc.filePath}`, - }; - }); - return { - type: AutocompleteSuggestionType.FILE, - total: res.total, - hasMore: res.total > this.MAX_SUGGESTIONS_PER_GROUP, - suggestions, - }; - } catch (error) { - return { - type: AutocompleteSuggestionType.FILE, - total: 0, - hasMore: false, - suggestions: [], - }; - } - } -} diff --git a/x-pack/legacy/plugins/code/public/components/query_bar/suggestions/index.ts b/x-pack/legacy/plugins/code/public/components/query_bar/suggestions/index.ts deleted file mode 100644 index 85d172aca5d79..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/query_bar/suggestions/index.ts +++ /dev/null @@ -1,32 +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 * from './suggestions_provider'; -export * from './symbol_suggestions_provider'; -export * from './file_suggestions_provider'; -export * from './repository_suggestions_provider'; - -export enum AutocompleteSuggestionType { - SYMBOL = 'symbol', - FILE = 'file', - REPOSITORY = 'repository', -} - -export interface AutocompleteSuggestion { - description?: string; - end: number; - start: number; - text: string; - tokenType: string; - selectUrl: string; -} - -export interface AutocompleteSuggestionGroup { - type: AutocompleteSuggestionType; - total: number; - hasMore: boolean; - suggestions: AutocompleteSuggestion[]; -} diff --git a/x-pack/legacy/plugins/code/public/components/query_bar/suggestions/repository_suggestions_provider.ts b/x-pack/legacy/plugins/code/public/components/query_bar/suggestions/repository_suggestions_provider.ts deleted file mode 100644 index 4696c11e1b90c..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/query_bar/suggestions/repository_suggestions_provider.ts +++ /dev/null @@ -1,61 +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 { npStart } from 'ui/new_platform'; - -import { - AbstractSuggestionsProvider, - AutocompleteSuggestionGroup, - AutocompleteSuggestionType, -} from '.'; -import { toRepoNameWithOrg } from '../../../../common/uri_util'; -import { Repository, SearchScope } from '../../../../model'; - -export class RepositorySuggestionsProvider extends AbstractSuggestionsProvider { - protected matchSearchScope(scope: SearchScope): boolean { - return scope === SearchScope.DEFAULT || scope === SearchScope.REPOSITORY; - } - - protected async fetchSuggestions( - query: string, - repoScope?: string[] - ): Promise { - try { - const queryParams: { q: string; repoScope?: string } = { q: query }; - if (repoScope && repoScope.length > 0) { - queryParams.repoScope = repoScope.join(','); - } - const res = await npStart.core.http.get(`/api/code/suggestions/repo`, { - query: queryParams, - }); - const suggestions = Array.from(res.repositories as Repository[]) - .slice(0, this.MAX_SUGGESTIONS_PER_GROUP) - .map((repo: Repository) => { - return { - description: repo.url, - end: 10, - start: 1, - text: toRepoNameWithOrg(repo.uri), - tokenType: '', - selectUrl: `/${repo.uri}`, - }; - }); - return { - type: AutocompleteSuggestionType.REPOSITORY, - total: res.total, - hasMore: res.total > this.MAX_SUGGESTIONS_PER_GROUP, - suggestions, - }; - } catch (error) { - return { - type: AutocompleteSuggestionType.REPOSITORY, - total: 0, - hasMore: false, - suggestions: [], - }; - } - } -} diff --git a/x-pack/legacy/plugins/code/public/components/query_bar/suggestions/suggestions_provider.ts b/x-pack/legacy/plugins/code/public/components/query_bar/suggestions/suggestions_provider.ts deleted file mode 100644 index ef9846bd4aed9..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/query_bar/suggestions/suggestions_provider.ts +++ /dev/null @@ -1,59 +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 { AutocompleteSuggestionGroup, AutocompleteSuggestionType } from '.'; -import { SearchScope } from '../../../../model'; - -export interface SuggestionsProvider { - getSuggestions( - query: string, - scope: SearchScope, - repoScope?: string[] - ): Promise; -} - -export abstract class AbstractSuggestionsProvider implements SuggestionsProvider { - protected MAX_SUGGESTIONS_PER_GROUP = 5; - - public async getSuggestions( - query: string, - scope: SearchScope, - repoScope?: string[] - ): Promise { - if (this.matchSearchScope(scope)) { - return await this.fetchSuggestions(query, repoScope); - } else { - // This is an abstract class. Do nothing here. You should override this. - return new Promise((resolve, reject) => { - resolve({ - type: AutocompleteSuggestionType.SYMBOL, - total: 0, - hasMore: false, - suggestions: [], - }); - }); - } - } - - protected async fetchSuggestions( - query: string, - repoScope?: string[] - ): Promise { - // This is an abstract class. Do nothing here. You should override this. - return new Promise((resolve, reject) => { - resolve({ - type: AutocompleteSuggestionType.SYMBOL, - total: 0, - hasMore: false, - suggestions: [], - }); - }); - } - - protected matchSearchScope(scope: SearchScope): boolean { - return true; - } -} diff --git a/x-pack/legacy/plugins/code/public/components/query_bar/suggestions/symbol_suggestions_provider.ts b/x-pack/legacy/plugins/code/public/components/query_bar/suggestions/symbol_suggestions_provider.ts deleted file mode 100644 index 0e321d6cf1ec7..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/query_bar/suggestions/symbol_suggestions_provider.ts +++ /dev/null @@ -1,142 +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 { DetailSymbolInformation } from '@elastic/lsp-extension'; -import { npStart } from 'ui/new_platform'; -import { Location } from 'vscode-languageserver'; - -import { - AbstractSuggestionsProvider, - AutocompleteSuggestion, - AutocompleteSuggestionGroup, - AutocompleteSuggestionType, -} from '.'; -import { RepositoryUtils } from '../../../../common/repository_utils'; -import { parseLspUrl, toRepoNameWithOrg } from '../../../../common/uri_util'; -import { SearchScope } from '../../../../model'; - -export class SymbolSuggestionsProvider extends AbstractSuggestionsProvider { - protected matchSearchScope(scope: SearchScope): boolean { - return scope === SearchScope.DEFAULT || scope === SearchScope.SYMBOL; - } - - protected async fetchSuggestions( - query: string, - repoScope?: string[] - ): Promise { - try { - const queryParams: { q: string; repoScope?: string } = { q: query }; - if (repoScope && repoScope.length > 0) { - queryParams.repoScope = repoScope.join(','); - } - const res = await npStart.core.http.get(`/api/code/suggestions/symbol`, { - query: queryParams, - }); - const suggestions = Array.from(res.symbols as DetailSymbolInformation[]) - .slice(0, this.MAX_SUGGESTIONS_PER_GROUP) - .map((symbol: DetailSymbolInformation) => { - return { - description: this.getSymbolDescription(symbol.symbolInformation.location), - end: 10, - start: 1, - text: symbol.qname ? symbol.qname : symbol.symbolInformation.name, - tokenType: this.symbolKindToTokenClass(symbol.symbolInformation.kind), - selectUrl: this.getSymbolLinkUrl(symbol.symbolInformation.location), - }; - }); - return { - type: AutocompleteSuggestionType.SYMBOL, - total: res.total, - hasMore: res.total > this.MAX_SUGGESTIONS_PER_GROUP, - suggestions: suggestions as AutocompleteSuggestion[], - }; - } catch (error) { - return { - type: AutocompleteSuggestionType.SYMBOL, - total: 0, - hasMore: false, - suggestions: [], - }; - } - } - - private getSymbolDescription(location: Location) { - try { - const { repoUri, file } = parseLspUrl(location.uri); - const repoName = toRepoNameWithOrg(repoUri); - return `${repoName} > ${file}`; - } catch (error) { - return ''; - } - } - - private getSymbolLinkUrl(location: Location) { - try { - return RepositoryUtils.locationToUrl(location); - } catch (error) { - return ''; - } - } - - private symbolKindToTokenClass(kind: number): string { - switch (kind) { - case 1: // File - return 'tokenFile'; - case 2: // Module - return 'tokenModule'; - case 3: // Namespace - return 'tokenNamespace'; - case 4: // Package - return 'tokenPackage'; - case 5: // Class - return 'tokenClass'; - case 6: // Method - return 'tokenMethod'; - case 7: // Property - return 'tokenProperty'; - case 8: // Field - return 'tokenField'; - case 9: // Constructor - return 'tokenConstant'; - case 10: // Enum - return 'tokenEnum'; - case 11: // Interface - return 'tokenInterface'; - case 12: // Function - return 'tokenFunction'; - case 13: // Variable - return 'tokenVariable'; - case 14: // Constant - return 'tokenConstant'; - case 15: // String - return 'tokenString'; - case 16: // Number - return 'tokenNumber'; - case 17: // Bollean - return 'tokenBoolean'; - case 18: // Array - return 'tokenArray'; - case 19: // Object - return 'tokenObject'; - case 20: // Key - return 'tokenKey'; - case 21: // Null - return 'tokenNull'; - case 22: // EnumMember - return 'tokenEnumMember'; - case 23: // Struct - return 'tokenStruct'; - case 24: // Event - return 'tokenEvent'; - case 25: // Operator - return 'tokenOperator'; - case 26: // TypeParameter - return 'tokenParameter'; - default: - return 'tokenElement'; - } - } -} diff --git a/x-pack/legacy/plugins/code/public/components/route.ts b/x-pack/legacy/plugins/code/public/components/route.ts deleted file mode 100644 index 826330d3f2d85..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/route.ts +++ /dev/null @@ -1,29 +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 { connect } from 'react-redux'; -import { Route as ReactRoute, RouteProps } from 'react-router-dom'; -import { Match, routeChange } from '../actions'; - -interface Props extends RouteProps { - routeChange: (match: Match) => void; - computedMatch?: any; -} -class CSRoute extends ReactRoute { - constructor(props: Props, context: any) { - super(props, context); - props.routeChange({ ...props.computedMatch, location: props.location }); - } - - public componentDidUpdate() { - this.props.routeChange({ ...this.state.match, location: this.props.location }); - } -} - -export const Route = connect( - null, - { routeChange } -)(CSRoute); diff --git a/x-pack/legacy/plugins/code/public/components/routes.ts b/x-pack/legacy/plugins/code/public/components/routes.ts deleted file mode 100644 index d741ca1896850..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/routes.ts +++ /dev/null @@ -1,18 +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 { PathTypes } from '../common/types'; - -export const ROOT = '/'; -export const SETUP = '/setup-guide'; -const pathTypes = `:pathType(${PathTypes.blob}|${PathTypes.tree}|${PathTypes.blame}|${PathTypes.commits})`; -export const MAIN = `/:resource/:org/:repo/${pathTypes}/:revision/:path*:goto(!.*)?`; -export const DIFF = '/:resource/:org/:repo/commit/:commitId'; -export const REPO = `/:resource/:org/:repo`; -export const MAIN_ROOT = `/:resource/:org/:repo/${pathTypes}/:revision`; -export const ADMIN = '/admin'; -export const SEARCH = '/search'; -export const INTEGRATIONS = '/integrations'; diff --git a/x-pack/legacy/plugins/code/public/components/search_bar/search_bar.tsx b/x-pack/legacy/plugins/code/public/components/search_bar/search_bar.tsx deleted file mode 100644 index 1841d09ba0d3d..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/search_bar/search_bar.tsx +++ /dev/null @@ -1,134 +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 querystring from 'querystring'; -import React from 'react'; -import url from 'url'; - -import { SearchOptions, SearchScope } from '../../../model'; -import { SearchScopeText } from '../../common/types'; -import { history } from '../../utils/url'; -import { ShortcutsProvider, Shortcut } from '../shortcuts'; - -import { - AutocompleteSuggestion, - FileSuggestionsProvider, - QueryBar, - RepositorySuggestionsProvider, - SymbolSuggestionsProvider, -} from '../query_bar'; - -interface Props { - query: string; - onSearchScopeChanged: (s: SearchScope) => void; - searchOptions: SearchOptions; - enableSubmitWhenOptionsChanged: boolean; -} - -export class SearchBar extends React.PureComponent { - public queryBar: any = null; - - public onSearchChanged = (query: string) => { - // Merge the default repository scope if necessary. - const repoScopes = this.props.searchOptions.repoScope.map(repo => repo.uri); - if (this.props.searchOptions.defaultRepoScopeOn && this.props.searchOptions.defaultRepoScope) { - repoScopes.push(this.props.searchOptions.defaultRepoScope.uri); - } - - // Update the url and push to history as well. - const previousQueries = querystring.parse(history.location.search.replace('?', '')); - const queries: any = { - ...previousQueries, - repoScope: repoScopes, - q: query, - }; - if (repoScopes.length === 0) { - delete queries.repoScope; - } - history.push( - url.format({ - pathname: '/search', - query: queries, - }) - ); - }; - - public toggleOptionsFlyout() { - if (this.queryBar) { - this.queryBar.toggleOptionsFlyout(); - } - } - - public onSubmit = (q: string) => { - // ignore empty query - if (q.trim().length > 0) { - this.onSearchChanged(q); - } - }; - - public onSelect = (item: AutocompleteSuggestion) => { - history.push(item.selectUrl); - }; - - public suggestionProviders = [ - new SymbolSuggestionsProvider(), - new FileSuggestionsProvider(), - new RepositorySuggestionsProvider(), - ]; - - public render() { - return ( -
- - { - this.props.onSearchScopeChanged(SearchScope.REPOSITORY); - if (this.queryBar) { - this.queryBar.focusInput(); - } - }} - /> - { - this.props.onSearchScopeChanged(SearchScope.SYMBOL); - if (this.queryBar) { - this.queryBar.focusInput(); - } - }} - /> - { - this.props.onSearchScopeChanged(SearchScope.DEFAULT); - if (this.queryBar) { - this.queryBar.focusInput(); - } - }} - /> - { - if (instance) { - // @ts-ignore - this.queryBar = instance.getWrappedInstance(); - } - }} - /> -
- ); - } -} diff --git a/x-pack/legacy/plugins/code/public/components/search_page/code_result.tsx b/x-pack/legacy/plugins/code/public/components/search_page/code_result.tsx deleted file mode 100644 index 022388f141b65..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/search_page/code_result.tsx +++ /dev/null @@ -1,96 +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 { EuiBadge, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; -import React from 'react'; -import { Link } from 'react-router-dom'; - -import { RepositoryUtils } from '../../../common/repository_utils'; -import { history } from '../../utils/url'; -import { CodeBlockPanel, Position } from '../code_block'; - -interface Props { - query: string; - results: any[]; -} - -export class CodeResult extends React.PureComponent { - public render() { - const { results, query } = this.props; - - return results.map(item => { - const { compositeContent, filePath, hits, language, uri } = item; - const { content, lineMapping, ranges } = compositeContent; - const key = `${uri}-${filePath}-${query}`; - const repoLinkUrl = `/${uri}/tree/HEAD/`; - const fileLinkUrl = `/${uri}/blob/HEAD/${filePath}`; // TODO(rylnd) move these to link helpers - const lines = content.split('\n'); - - return ( -
-
- - - - - {RepositoryUtils.orgNameFromUri(uri)}/ - - - - - {RepositoryUtils.repoNameFromUri(uri)} - - - - -
- - - {hits} - - - - - {filePath} - - - - lineMapping[i]} - onClick={this.onCodeClick(fileLinkUrl)} - /> -
- ); - }); - } - - private onCodeClick = (url: string) => (position: Position) => { - const lineNumber = parseInt(position.lineNumber, 10); - - if (!isNaN(lineNumber)) { - history.push(`${url}!L${lineNumber}:0`); - } - }; -} diff --git a/x-pack/legacy/plugins/code/public/components/search_page/empty_placeholder.tsx b/x-pack/legacy/plugins/code/public/components/search_page/empty_placeholder.tsx deleted file mode 100644 index f5117d97471f8..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/search_page/empty_placeholder.tsx +++ /dev/null @@ -1,59 +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 { EuiButton, EuiSpacer, EuiText } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; -import React from 'react'; - -export const EmptyPlaceholder = (props: any) => { - return ( -
- - - - " - - {props.query} - - " - - - - - - - - - - - - { - if (props.toggleOptionsFlyout) { - props.toggleOptionsFlyout(); - } - }} - > - - - -
- ); -}; diff --git a/x-pack/legacy/plugins/code/public/components/search_page/pagination.tsx b/x-pack/legacy/plugins/code/public/components/search_page/pagination.tsx deleted file mode 100644 index bc3ee4384b9b9..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/search_page/pagination.tsx +++ /dev/null @@ -1,49 +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 { EuiFlexGroup, EuiFlexItem, EuiPagination } from '@elastic/eui'; -import querystring from 'querystring'; -import React from 'react'; -import url from 'url'; - -import { history } from '../../utils/url'; - -interface Props { - query: string; - totalPage: number; - currentPage: number; -} - -export class Pagination extends React.PureComponent { - public onPageClicked = (page: number) => { - const { query } = this.props; - const queries = querystring.parse(history.location.search.replace('?', '')); - history.push( - url.format({ - pathname: '/search', - query: { - ...queries, - q: query, - p: page + 1, - }, - }) - ); - }; - - public render() { - return ( - - - - - - ); - } -} diff --git a/x-pack/legacy/plugins/code/public/components/search_page/search.scss b/x-pack/legacy/plugins/code/public/components/search_page/search.scss deleted file mode 100644 index febad588af3c6..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/search_page/search.scss +++ /dev/null @@ -1,3 +0,0 @@ -.codeResult__code-block { - margin-bottom: $euiSizeXL; -} diff --git a/x-pack/legacy/plugins/code/public/components/search_page/search.tsx b/x-pack/legacy/plugins/code/public/components/search_page/search.tsx deleted file mode 100644 index 8ccc46d09502f..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/search_page/search.tsx +++ /dev/null @@ -1,269 +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 { EuiFlexItem, EuiLoadingSpinner, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; -import querystring from 'querystring'; -import React from 'react'; -import { connect } from 'react-redux'; -import url from 'url'; -import { npStart } from 'ui/new_platform'; - -import { APP_TITLE } from '../../../common/constants'; -import { DocumentSearchResult, SearchOptions, SearchScope } from '../../../model'; -import { changeSearchScope } from '../../actions'; -import { RootState } from '../../reducers'; -import { history } from '../../utils/url'; -import { ProjectItem } from '../admin_page/project_item'; -import { SearchBar } from '../search_bar'; -import { CodeResult } from './code_result'; -import { EmptyPlaceholder } from './empty_placeholder'; -import { Pagination } from './pagination'; -import { SideBar } from './side_bar'; -import { trackCodeUiMetric, METRIC_TYPE } from '../../services/ui_metric'; -import { CodeUIUsageMetrics } from '../../../model/usage_telemetry_metrics'; - -interface Props { - searchOptions: SearchOptions; - query: string; - scope: SearchScope; - page?: number; - languages?: Set; - repositories?: Set; - isLoading: boolean; - error?: Error; - documentSearchResults?: DocumentSearchResult; - repositorySearchResults?: any; - onSearchScopeChanged: (s: SearchScope) => void; -} - -interface State { - uri: string; -} - -class SearchPage extends React.PureComponent { - public state = { - uri: '', - }; - - public searchBar: any = null; - - public componentDidMount() { - // track search page load count - trackCodeUiMetric(METRIC_TYPE.LOADED, CodeUIUsageMetrics.SEARCH_PAGE_LOAD_COUNT); - npStart.core.chrome.setBreadcrumbs([ - { text: APP_TITLE, href: '#/' }, - { - text: 'Search', - }, - ]); - } - - public componentWillUnmount() { - npStart.core.chrome.setBreadcrumbs([{ text: APP_TITLE, href: '#/' }]); - } - - public onLanguageFilterToggled = (lang: string) => { - const { languages, repositories, query, page } = this.props; - let tempLangs: Set = new Set(); - if (languages && languages.has(lang)) { - // Remove this language filter - tempLangs = new Set(languages); - tempLangs.delete(lang); - } else { - // Add this language filter - tempLangs = languages ? new Set(languages) : new Set(); - tempLangs.add(lang); - } - const queries = querystring.parse(history.location.search.replace('?', '')); - return () => { - history.push( - url.format({ - pathname: '/search', - query: { - ...queries, - q: query, - p: page, - langs: Array.from(tempLangs).join(','), - repos: repositories ? Array.from(repositories).join(',') : undefined, - }, - }) - ); - }; - }; - - public onRepositoryFilterToggled = (repo: string) => { - const { languages, repositories, query } = this.props; - let tempRepos: Set = new Set(); - if (repositories && repositories.has(repo)) { - // Remove this repository filter - tempRepos = new Set(repositories); - tempRepos.delete(repo); - } else { - // Add this language filter - tempRepos = repositories ? new Set(repositories) : new Set(); - tempRepos.add(repo); - } - const queries = querystring.parse(history.location.search.replace('?', '')); - return () => { - history.push( - url.format({ - pathname: '/search', - query: { - ...queries, - q: query, - p: 1, - langs: languages ? Array.from(languages).join(',') : undefined, - repos: Array.from(tempRepos).join(','), - }, - }) - ); - }; - }; - - public render() { - const { - query, - scope, - documentSearchResults, - languages, - isLoading, - repositories, - repositorySearchResults, - } = this.props; - - let mainComp = isLoading ? ( -
- - - Loading... - - - - -
- ) : ( - { - this.searchBar.toggleOptionsFlyout(); - }} - /> - ); - let repoStats: any[] = []; - let languageStats: any[] = []; - if ( - scope === SearchScope.REPOSITORY && - repositorySearchResults && - repositorySearchResults.total > 0 - ) { - const { repositories: repos, from, total } = repositorySearchResults; - const resultComps = - repos && - repos.map((repo: any) => ( - - - - )); - const to = from + repos.length; - const statsComp = ( - -

- -

-
- ); - mainComp = ( -
- {statsComp} - -
{resultComps}
-
- ); - } else if (scope === SearchScope.DEFAULT && documentSearchResults) { - const { stats, results } = documentSearchResults!; - const { total, from, to, page, totalPage } = stats!; - languageStats = stats!.languageStats; - repoStats = stats!.repoStats; - if (documentSearchResults.total > 0) { - const statsComp = ( - -

- -

-
- ); - mainComp = ( -
- {statsComp} - -
- -
- -
- ); - } - } - - return ( -
- (this.searchBar = element)} - /> -
- -
{mainComp}
-
-
- ); - } -} - -const mapStateToProps = (state: RootState) => ({ - ...state.search, -}); - -const mapDispatchToProps = { - onSearchScopeChanged: changeSearchScope, -}; - -export const Search = connect( - mapStateToProps, - mapDispatchToProps -)(SearchPage); diff --git a/x-pack/legacy/plugins/code/public/components/search_page/side_bar.tsx b/x-pack/legacy/plugins/code/public/components/search_page/side_bar.tsx deleted file mode 100644 index 3bcc6f9dce3b2..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/search_page/side_bar.tsx +++ /dev/null @@ -1,120 +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 { - EuiFacetButton, - EuiFacetGroup, - EuiFlexGroup, - EuiFlexItem, - EuiTitle, - EuiToken, -} from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; -import React from 'react'; - -import { RepositoryUtils } from '../../../common/repository_utils'; -import { SearchScope } from '../../../model'; - -interface Props { - query: string; - scope: SearchScope; - languages?: Set; - repositories?: Set; - langFacets: any[]; - repoFacets: any[]; - onLanguageFilterToggled: (lang: string) => any; - onRepositoryFilterToggled: (repo: string) => any; -} - -export class SideBar extends React.PureComponent { - voidFunc = () => void 0; - - renderLangFacets = () => { - return this.props.langFacets.map((item, index) => { - const isSelected = this.props.languages && this.props.languages.has(item.name); - return ( - - {item.name} - - ); - }); - }; - - renderRepoFacets = () => { - return this.props.repoFacets.map((item, index) => { - const isSelected = !!this.props.repositories && this.props.repositories.has(item.name); - return ( - - {RepositoryUtils.repoNameFromUri(item.name)} - - ); - }); - }; - - public render() { - return ( -
-
- - - - - - -

- -

-
-
-
- {this.renderRepoFacets()} - - - - - - -

- -

-
-
-
- - {this.renderLangFacets()} - -
-
- ); - } -} diff --git a/x-pack/legacy/plugins/code/public/components/shared/icons.tsx b/x-pack/legacy/plugins/code/public/components/shared/icons.tsx deleted file mode 100644 index 1ee9c2faaf8de..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/shared/icons.tsx +++ /dev/null @@ -1,344 +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 React from 'react'; - -export const TypeScriptIcon = () => ( - - - - - - -); - -export const JavaIcon = () => ( - - - - - - -); - -export const GoIcon = () => ( - - - - - - - -); - -export const CtagsIcon = () => ( - - - -); - -export const BinaryFileIcon = () => ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -); - -export const ErrorIcon = () => ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -); diff --git a/x-pack/legacy/plugins/code/public/components/shared/resize_checker.tsx b/x-pack/legacy/plugins/code/public/components/shared/resize_checker.tsx deleted file mode 100644 index 9693f4870c5b8..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/shared/resize_checker.tsx +++ /dev/null @@ -1,111 +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. - */ - -/** - * Copied from `ui/resize_checker` because of NP migration. - */ - -import { EventEmitter } from 'events'; -import $ from 'jquery'; -import { isEqual } from 'lodash'; -import ResizeObserver from 'resize-observer-polyfill'; - -function validateElArg(el: HTMLElement) { - // the ResizeChecker historically accepted jquery elements, - // so we wrap in jQuery then extract the element - const $el = $(el); - - if ($el.length !== 1) { - throw new TypeError('ResizeChecker must be constructed with a single DOM element.'); - } - - return $el.get(0); -} - -function getSize(el: HTMLElement): [number, number] { - return [el.clientWidth, el.clientHeight]; -} - -/** - * ResizeChecker receives an element and emits a "resize" event every time it changes size. - */ -export class ResizeChecker extends EventEmitter { - private destroyed: boolean = false; - private el: HTMLElement | null; - private observer: ResizeObserver | null; - private expectedSize: [number, number] | null = null; - - constructor(el: HTMLElement, args: { disabled?: boolean } = {}) { - super(); - - this.el = validateElArg(el); - - this.observer = new ResizeObserver(() => { - if (this.expectedSize) { - const sameSize = isEqual(getSize(el), this.expectedSize); - this.expectedSize = null; - - if (sameSize) { - // don't trigger resize notification if the size is what we expect - return; - } - } - - this.emit('resize'); - }); - - // Only enable the checker immediately if args.disabled wasn't set to true - if (!args.disabled) { - this.enable(); - } - } - - public enable() { - if (this.destroyed) { - // Don't allow enabling an already destroyed resize checker - return; - } - // the width and height of the element that we expect to see - // on the next resize notification. If it matches the size at - // the time of starting observing then it we will be ignored. - // We know that observer and el are not null since we are not yet destroyed. - this.expectedSize = getSize(this.el!); - this.observer!.observe(this.el!); - } - - /** - * Run a function and ignore all resizes that occur - * while it's running. - */ - public modifySizeWithoutTriggeringResize(block: () => void): void { - try { - block(); - } finally { - if (this.el) { - this.expectedSize = getSize(this.el); - } - } - } - - /** - * Tell the ResizeChecker to shutdown, stop listenings, and never - * emit another resize event. - * - * Cleans up it's listeners and timers. - */ - public destroy(): void { - if (this.destroyed) { - return; - } - this.destroyed = true; - - this.observer!.disconnect(); - this.observer = null; - this.expectedSize = null; - this.el = null; - this.removeAllListeners(); - } -} diff --git a/x-pack/legacy/plugins/code/public/components/shortcuts/index.tsx b/x-pack/legacy/plugins/code/public/components/shortcuts/index.tsx deleted file mode 100644 index a56629a10c415..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/shortcuts/index.tsx +++ /dev/null @@ -1,8 +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 { Shortcut, OS, HotKey, Modifier } from './shortcut'; -export { ShortcutsProvider } from './shortcuts_provider'; diff --git a/x-pack/legacy/plugins/code/public/components/shortcuts/shortcut.tsx b/x-pack/legacy/plugins/code/public/components/shortcuts/shortcut.tsx deleted file mode 100644 index 7e5afc8b25e47..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/shortcuts/shortcut.tsx +++ /dev/null @@ -1,84 +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 React from 'react'; -import { connect } from 'react-redux'; -import { registerShortcut, unregisterShortcut } from '../../actions'; - -export enum OS { - win, - mac, - linux, -} - -export enum Modifier { - ctrl, - meta, - alt, - shift, -} - -export interface HotKey { - key: string; - modifier: Map; - help: string; - onPress?: (dispatch: any) => void; -} - -interface Props { - keyCode: string; - help: string; - onPress?: (dispatch: any) => void; - winModifier?: Modifier[]; - macModifier?: Modifier[]; - linuxModifier?: Modifier[]; - registerShortcut(hotKey: HotKey): void; - unregisterShortcut(hotKey: HotKey): void; -} - -class ShortcutsComponent extends React.Component { - private readonly hotKey: HotKey; - constructor(props: Props, context: any) { - super(props, context); - this.hotKey = { - key: props.keyCode, - help: props.help, - onPress: props.onPress, - modifier: new Map(), - }; - if (props.winModifier) { - this.hotKey.modifier.set(OS.win, props.winModifier); - } - if (props.macModifier) { - this.hotKey.modifier.set(OS.mac, props.macModifier); - } - if (props.linuxModifier) { - this.hotKey.modifier.set(OS.linux, props.linuxModifier); - } - } - - public componentDidMount(): void { - this.props.registerShortcut(this.hotKey); - } - - public componentWillUnmount(): void { - this.props.unregisterShortcut(this.hotKey); - } - - public render(): React.ReactNode { - return null; - } -} - -const mapDispatchToProps = { - registerShortcut, - unregisterShortcut, -}; - -export const Shortcut = connect( - null, - mapDispatchToProps -)(ShortcutsComponent); diff --git a/x-pack/legacy/plugins/code/public/components/shortcuts/shortcuts_provider.tsx b/x-pack/legacy/plugins/code/public/components/shortcuts/shortcuts_provider.tsx deleted file mode 100644 index 229e0a50dda35..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/shortcuts/shortcuts_provider.tsx +++ /dev/null @@ -1,203 +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 { - EuiButton, - EuiModal, - EuiModalBody, - EuiModalFooter, - EuiModalHeader, - EuiModalHeaderTitle, - EuiOverlayMask, -} from '@elastic/eui'; -import React from 'react'; -import { connect } from 'react-redux'; -import { toggleHelp } from '../../actions'; -import { RootState } from '../../reducers'; -import { HotKey, Modifier, OS } from './shortcut'; - -interface Props { - showHelp: boolean; - shortcuts: HotKey[]; - dispatch(action: any): void; -} - -class ShortcutsComponent extends React.Component { - private readonly os: OS; - - constructor(props: Props) { - super(props); - - if (navigator.appVersion.indexOf('Win') !== -1) { - this.os = OS.win; - } else if (navigator.appVersion.indexOf('Mac') !== -1) { - this.os = OS.mac; - } else { - this.os = OS.linux; - } - } - - public componentDidMount(): void { - document.addEventListener('keydown', this.handleKeydown); - document.addEventListener('keypress', this.handleKeyPress); - } - - public componentWillUnmount(): void { - document.removeEventListener('keydown', this.handleKeydown); - document.removeEventListener('keypress', this.handleKeyPress); - } - - public render(): React.ReactNode { - return ( - - {this.props.showHelp && ( - - - - Keyboard Shortcuts - - - {this.renderShortcuts()} - - - Close - - - - - )} - - ); - } - - private handleKeydown = (event: KeyboardEvent) => { - const target = event.target; - const key = event.key; - // @ts-ignore - if (target && target.tagName === 'INPUT') { - if (key === 'Escape') { - // @ts-ignore - target.blur(); - } - } - }; - - private handleKeyPress = (event: KeyboardEvent) => { - const target = event.target; - const key = event.key; - // @ts-ignore - if (target && target.tagName === 'INPUT') { - return; - } - - const isPressed = (s: HotKey) => { - if (s.modifier) { - const mods = s.modifier.get(this.os) || []; - for (const mod of mods) { - switch (mod) { - case Modifier.alt: - if (!event.altKey) { - return false; - } - break; - case Modifier.ctrl: - if (!event.ctrlKey) { - return false; - } - break; - case Modifier.meta: - if (!event.metaKey) { - return false; - } - break; - case Modifier.shift: - if (!event.shiftKey) { - return false; - } - break; - } - } - } - return key === s.key; - }; - - let isTriggered = false; - for (const shortcut of this.props.shortcuts) { - if (isPressed(shortcut) && shortcut.onPress) { - shortcut.onPress(this.props.dispatch); - isTriggered = true; - } - } - if (isTriggered) { - // Discard this input since it's been triggered already. - event.preventDefault(); - } - }; - - private closeModal = () => { - this.props.dispatch(toggleHelp(false)); - }; - - private showModifier(mod: Modifier): string { - switch (mod) { - case Modifier.meta: - if (this.os === OS.mac) { - return '⌘'; - } else if (this.os === OS.win) { - return '⊞ Win'; - } else { - return 'meta'; - } - - case Modifier.shift: - if (this.os === OS.mac) { - return '⇧'; - } else { - return 'shift'; - } - case Modifier.ctrl: - if (this.os === OS.mac) { - return '⌃'; - } else { - return 'ctrl'; - } - case Modifier.alt: - if (this.os === OS.mac) { - return '⌥'; - } else { - return 'alt'; - } - } - } - - private renderShortcuts() { - return this.props.shortcuts.map((s, idx) => { - return ( -
- {this.renderModifier(s)} - {s.key} - {s.help} -
- ); - }); - } - - private renderModifier(hotKey: HotKey) { - if (hotKey.modifier) { - const modifiers = hotKey.modifier.get(this.os) || []; - return modifiers.map(m =>
{this.showModifier(m)}
); - } else { - return null; - } - } -} - -const mapStateToProps = (state: RootState) => ({ - shortcuts: state.shortcuts.shortcuts, - showHelp: state.shortcuts.showHelp, -}); - -export const ShortcutsProvider = connect(mapStateToProps)(ShortcutsComponent); diff --git a/x-pack/legacy/plugins/code/public/components/status_indicator/alert.svg b/x-pack/legacy/plugins/code/public/components/status_indicator/alert.svg deleted file mode 100644 index d84e1220a39dc..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/status_indicator/alert.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/x-pack/legacy/plugins/code/public/components/status_indicator/blank.svg b/x-pack/legacy/plugins/code/public/components/status_indicator/blank.svg deleted file mode 100644 index 3465aea3d970e..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/status_indicator/blank.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/x-pack/legacy/plugins/code/public/components/status_indicator/error.svg b/x-pack/legacy/plugins/code/public/components/status_indicator/error.svg deleted file mode 100644 index 3d2f47ceac51a..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/status_indicator/error.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/x-pack/legacy/plugins/code/public/components/status_indicator/info.svg b/x-pack/legacy/plugins/code/public/components/status_indicator/info.svg deleted file mode 100644 index 787282c8a9e77..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/status_indicator/info.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/x-pack/legacy/plugins/code/public/components/status_indicator/status_indicator.tsx b/x-pack/legacy/plugins/code/public/components/status_indicator/status_indicator.tsx deleted file mode 100644 index 4c4454c2003ea..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/status_indicator/status_indicator.tsx +++ /dev/null @@ -1,147 +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 React from 'react'; -import { EuiButtonIcon, EuiPopover, EuiText } from '@elastic/eui'; -import { connect } from 'react-redux'; -import { Link } from 'react-router-dom'; -import ErrorSvg from './error.svg'; -import InfoSvg from './info.svg'; -import AlertSvg from './alert.svg'; -import BlankSvg from './blank.svg'; -import { - CTA, - LangServerType, - REPO_FILE_STATUS_SEVERITY, - RepoFileStatus, - RepoFileStatusText as StatusText, - Severity, - StatusReport, -} from '../../../common/repo_file_status'; -import { RootState } from '../../reducers'; -import { FetchFilePayload } from '../../actions'; - -interface Props { - statusReport?: StatusReport; - currentStatusPath?: FetchFilePayload; - pathType: string; -} - -interface State { - isPopoverOpen: boolean; -} - -const svgs = { - [Severity.NOTICE]: InfoSvg, - [Severity.WARNING]: AlertSvg, - [Severity.ERROR]: ErrorSvg, - [Severity.NONE]: BlankSvg, -}; - -export class StatusIndicatorComponent extends React.Component { - constructor(props: Readonly) { - super(props); - this.state = { - isPopoverOpen: false, - }; - } - - closePopover() { - this.setState({ - isPopoverOpen: false, - }); - } - openPopover() { - this.setState({ - isPopoverOpen: true, - }); - } - - render() { - const { statusReport } = this.props; - let severity = Severity.NONE; - const children: any[] = []; - const addError = (error: RepoFileStatus | LangServerType) => { - // @ts-ignore - const s: any = REPO_FILE_STATUS_SEVERITY[error]; - if (s) { - if (s.severity > severity) { - severity = s.severity; - } - const fix = s.fix; - if (fix !== undefined) { - const fixUrl = this.fixUrl(fix); - children.push( -

- {error} You can {fixUrl}. -

- ); - } else { - children.push(

{StatusText[error]}

); - } - } - }; - if (statusReport) { - if (statusReport.repoStatus) { - addError(statusReport.repoStatus); - } - if (statusReport.fileStatus) { - addError(statusReport.fileStatus); - } - if (statusReport.langServerType === LangServerType.GENERIC) { - addError(statusReport.langServerType); - } - if (statusReport.langServerStatus) { - addError(statusReport.langServerStatus); - } - } - const svg = svgs[severity]; - const icon = ( - - ); - if (children.length === 0) { - return
; - } - - return ( - - {children} - - ); - } - - private fixUrl(fix: CTA) { - switch (fix) { - case CTA.GOTO_LANG_MANAGE_PAGE: - return install it here; - case CTA.SWITCH_TO_HEAD: - const { uri, path } = this.props.currentStatusPath!; - const headUrl = path - ? `/${uri}/${this.props.pathType}/HEAD/${path}` - : `/${uri}/${this.props.pathType}/HEAD/`; - - return switch to HEAD; - } - } -} - -const mapStateToProps = (state: RootState) => ({ - statusReport: state.status.repoFileStatus, - currentStatusPath: state.status.currentStatusPath, - pathType: state.route.match.params.pathType, -}); - -export const StatusIndicator = connect(mapStateToProps)(StatusIndicatorComponent); diff --git a/x-pack/legacy/plugins/code/public/components/symbol_tree/__test__/__fixtures__/props.ts b/x-pack/legacy/plugins/code/public/components/symbol_tree/__test__/__fixtures__/props.ts deleted file mode 100644 index 165957553d5dd..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/symbol_tree/__test__/__fixtures__/props.ts +++ /dev/null @@ -1,119 +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 { SymbolKind } from 'vscode-languageserver-types'; -import { SymbolWithMembers } from '../../../../actions/structure'; - -export const props: { structureTree: SymbolWithMembers[]; uri: string } = { - uri: - 'git://github.com/vmware/clarity/blob/master/src/clr-angular/data/stack-view/stack-control.ts', - structureTree: [ - { - name: '"stack-control"', - kind: SymbolKind.Module, - range: { start: { line: 0, character: 0 }, end: { line: 27, character: 0 } }, - selectionRange: { start: { line: 0, character: 0 }, end: { line: 27, character: 0 } }, - path: '"stack-control"', - members: [ - { - name: 'EventEmitter', - kind: SymbolKind.Variable, - range: { start: { line: 9, character: 9 }, end: { line: 9, character: 21 } }, - selectionRange: { start: { line: 9, character: 9 }, end: { line: 9, character: 21 } }, - path: '"stack-control"/EventEmitter', - }, - { - name: 'ClrStackView', - kind: SymbolKind.Variable, - range: { start: { line: 10, character: 9 }, end: { line: 10, character: 21 } }, - selectionRange: { start: { line: 10, character: 9 }, end: { line: 10, character: 21 } }, - path: '"stack-control"/ClrStackView', - }, - { - name: 'StackControl', - kind: SymbolKind.Class, - range: { start: { line: 12, character: 0 }, end: { line: 26, character: 1 } }, - selectionRange: { start: { line: 12, character: 0 }, end: { line: 26, character: 1 } }, - path: '"stack-control"/StackControl', - members: [ - { - name: 'model', - kind: SymbolKind.Property, - range: { start: { line: 13, character: 2 }, end: { line: 13, character: 13 } }, - selectionRange: { - start: { line: 13, character: 2 }, - end: { line: 13, character: 13 }, - }, - path: '"stack-control"/StackControl/model', - }, - { - name: 'modelChange', - kind: SymbolKind.Property, - range: { start: { line: 14, character: 2 }, end: { line: 14, character: 64 } }, - selectionRange: { - start: { line: 14, character: 2 }, - end: { line: 14, character: 64 }, - }, - path: '"stack-control"/StackControl/modelChange', - }, - { - name: 'stackView', - kind: SymbolKind.Property, - range: { start: { line: 16, character: 14 }, end: { line: 16, character: 47 } }, - selectionRange: { - start: { line: 16, character: 14 }, - end: { line: 16, character: 47 }, - }, - path: '"stack-control"/StackControl/stackView', - }, - { - name: 'HashMap', - kind: SymbolKind.Class, - range: { start: { line: 136, character: 13 }, end: { line: 136, character: 20 } }, - selectionRange: { - start: { line: 136, character: 13 }, - end: { line: 136, character: 20 }, - }, - path: 'HashMap', - members: [ - { - name: 'serialVersionUID', - kind: SymbolKind.Field, - range: { start: { line: 139, character: 30 }, end: { line: 139, character: 46 } }, - selectionRange: { - start: { line: 139, character: 30 }, - end: { line: 139, character: 46 }, - }, - path: 'HashMap/serialVersionUID', - }, - ], - }, - { - name: 'Unit', - kind: SymbolKind.Variable, - range: { start: { line: 20, character: 0 }, end: { line: 20, character: 66 } }, - selectionRange: { - start: { line: 20, character: 0 }, - end: { line: 20, character: 66 }, - }, - path: 'Unit', - }, - { - name: 'datemath', - kind: SymbolKind.Constant, - range: { start: { line: 22, character: 14 }, end: { line: 47, character: 1 } }, - selectionRange: { - start: { line: 22, character: 14 }, - end: { line: 47, character: 1 }, - }, - path: 'datemath', - }, - ], - }, - ], - }, - ], -}; diff --git a/x-pack/legacy/plugins/code/public/components/symbol_tree/__test__/__snapshots__/symbol_tree.test.tsx.snap b/x-pack/legacy/plugins/code/public/components/symbol_tree/__test__/__snapshots__/symbol_tree.test.tsx.snap deleted file mode 100644 index 975e1df6777d8..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/symbol_tree/__test__/__snapshots__/symbol_tree.test.tsx.snap +++ /dev/null @@ -1,553 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`render symbol tree correctly 1`] = ` - -`; diff --git a/x-pack/legacy/plugins/code/public/components/symbol_tree/__test__/symbol_tree.test.tsx b/x-pack/legacy/plugins/code/public/components/symbol_tree/__test__/symbol_tree.test.tsx deleted file mode 100644 index 47a265512bfbd..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/symbol_tree/__test__/symbol_tree.test.tsx +++ /dev/null @@ -1,60 +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 React from 'react'; -import { MemoryRouter, match } from 'react-router-dom'; -import renderer from 'react-test-renderer'; -import { - mockFunction, - createLocation, - createMatch, - createHistory, -} from '../../../utils/test_utils'; -import { CodeSymbolTree } from '../code_symbol_tree'; -import { props } from './__fixtures__/props'; -import { MainRouteParams, PathTypes } from '../../../common/types'; -import { History, Location } from 'history'; - -const location: Location = createLocation({ - pathname: - '/github.com/vmware/clarity/blob/master/src/clr-angular/data/stack-view/stack-control.ts', -}); - -const m: match = createMatch({ - path: '/:resource/:org/:repo/:pathType(blob|tree)/:revision/:path*:goto(!.*)?', - url: '/github.com/vmware/clarity/blob/master/src/clr-angular/data/stack-view/stack-control.ts', - isExact: true, - params: { - resource: 'github.com', - org: 'google', - repo: 'vmware', - pathType: PathTypes.blob, - revision: 'master', - path: 'src/clr-angular/data/stack-view/stack-control.ts', - }, -}); - -const history: History = createHistory({ location, length: 8, action: 'POP' }); - -test('render symbol tree correctly', () => { - const tree = renderer - .create( - - - - ) - .toJSON(); - expect(tree).toMatchSnapshot(); -}); diff --git a/x-pack/legacy/plugins/code/public/components/symbol_tree/code_symbol_tree.tsx b/x-pack/legacy/plugins/code/public/components/symbol_tree/code_symbol_tree.tsx deleted file mode 100644 index 58038c2d59008..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/symbol_tree/code_symbol_tree.tsx +++ /dev/null @@ -1,155 +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 { EuiFlexGroup, EuiIcon, EuiSideNav, EuiText, EuiToken } from '@elastic/eui'; -import { IconType } from '@elastic/eui'; -import React from 'react'; -import { Link, RouteComponentProps } from 'react-router-dom'; -import url from 'url'; -import { Range, SymbolKind } from 'vscode-languageserver-types'; -import { isEqual } from 'lodash'; - -import { RepositoryUtils } from '../../../common/repository_utils'; -import { EuiSideNavItem, MainRouteParams } from '../../common/types'; -import { SymbolWithMembers } from '../../actions/structure'; -import { trackCodeUiMetric, METRIC_TYPE } from '../../services/ui_metric'; -import { CodeUIUsageMetrics } from '../../../model/usage_telemetry_metrics'; - -interface Props extends RouteComponentProps { - structureTree: SymbolWithMembers[]; - closedPaths: string[]; - openSymbolPath: (p: string) => void; - closeSymbolPath: (p: string) => void; - uri: string; -} - -interface ActiveSymbol { - name: string; - range: Range; -} - -export class CodeSymbolTree extends React.PureComponent { - public state: { activeSymbol?: ActiveSymbol } = {}; - - public componentDidUpdate(prevProps: Props) { - if (this.props.uri && prevProps.uri !== this.props.uri && this.props.structureTree.length > 0) { - // track lsp data available page view count - trackCodeUiMetric(METRIC_TYPE.COUNT, CodeUIUsageMetrics.LSP_DATA_AVAILABLE_PAGE_VIEW_COUNT); - } - } - - public getClickHandler = (symbol: ActiveSymbol) => () => { - this.setState({ activeSymbol: symbol }); - // track structure tree click count - trackCodeUiMetric(METRIC_TYPE.COUNT, CodeUIUsageMetrics.STRUCTURE_TREE_CLICK_COUNT); - }; - - public getStructureTreeItemRenderer = ( - range: Range, - name: string, - kind: SymbolKind, - isContainer: boolean = false, - forceOpen: boolean = false, - path: string = '' - ) => () => { - let tokenType = 'tokenFile'; - - // @ts-ignore - tokenType = `token${Object.keys(SymbolKind).find(k => SymbolKind[k] === kind)}`; - let bg = null; - if ( - this.state.activeSymbol && - this.state.activeSymbol.name === name && - isEqual(this.state.activeSymbol.range, range) - ) { - bg =
; - } - const queries = url.parse(this.props.location.search, true).query; - return ( -
- {bg} - -
- {isContainer && - (forceOpen ? ( - this.props.closeSymbolPath(path)} - /> - ) : ( - this.props.openSymbolPath(path)} - /> - ))} - - - {name} - -
-
- ); - }; - - public symbolsToSideNavItems = (symbolsWithMembers: SymbolWithMembers[]): EuiSideNavItem[] => { - return symbolsWithMembers.map((s: SymbolWithMembers, index: number) => { - const item: EuiSideNavItem = { - name: s.name, - id: `${s.name}_${index}`, - onClick: () => void 0, - }; - if (s.members) { - item.forceOpen = !this.props.closedPaths.includes(s.path!); - if (item.forceOpen) { - item.items = this.symbolsToSideNavItems(s.members); - } - item.renderItem = this.getStructureTreeItemRenderer( - s.selectionRange, - s.name, - s.kind, - s.members.length > 0, - item.forceOpen, - s.path - ); - } else { - item.renderItem = this.getStructureTreeItemRenderer( - s.selectionRange, - s.name, - s.kind, - false, - false, - s.path - ); - } - return item; - }); - }; - - public render() { - const items = [ - { name: '', id: '', items: this.symbolsToSideNavItems(this.props.structureTree) }, - ]; - return ( -
- -
- ); - } -} diff --git a/x-pack/legacy/plugins/code/public/components/symbol_tree/symbol_tree.tsx b/x-pack/legacy/plugins/code/public/components/symbol_tree/symbol_tree.tsx deleted file mode 100644 index 82f8252724f41..0000000000000 --- a/x-pack/legacy/plugins/code/public/components/symbol_tree/symbol_tree.tsx +++ /dev/null @@ -1,29 +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 { connect } from 'react-redux'; -import { withRouter } from 'react-router-dom'; -import { closeSymbolPath, openSymbolPath } from '../../actions'; -import { RootState } from '../../reducers'; -import { structureSelector } from '../../selectors'; -import { CodeSymbolTree } from './code_symbol_tree'; - -const mapStateToProps = (state: RootState) => ({ - structureTree: structureSelector(state), - closedPaths: state.symbol.closedPaths, -}); - -const mapDispatchToProps = { - openSymbolPath, - closeSymbolPath, -}; - -export const SymbolTree = withRouter( - connect( - mapStateToProps, - mapDispatchToProps - )(CodeSymbolTree) -); diff --git a/x-pack/legacy/plugins/code/public/hacks/toggle_app_link_in_nav.ts b/x-pack/legacy/plugins/code/public/hacks/toggle_app_link_in_nav.ts deleted file mode 100644 index 0e648d03c519d..0000000000000 --- a/x-pack/legacy/plugins/code/public/hacks/toggle_app_link_in_nav.ts +++ /dev/null @@ -1,12 +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 { npStart } from 'ui/new_platform'; - -const codeUiEnabled = npStart.core.injectedMetadata.getInjectedVar('codeUiEnabled'); -if (codeUiEnabled === false) { - npStart.core.chrome.navLinks.update('code', { hidden: true }); -} diff --git a/x-pack/legacy/plugins/code/public/index.scss b/x-pack/legacy/plugins/code/public/index.scss deleted file mode 100644 index 43768f7758ea1..0000000000000 --- a/x-pack/legacy/plugins/code/public/index.scss +++ /dev/null @@ -1,23 +0,0 @@ -@import 'src/legacy/ui/public/styles/_styling_constants'; - -@import "./components/editor/references_panel.scss"; -@import "./monaco/override_monaco_styles.scss"; -@import "./components/diff_page/diff.scss"; -@import "./components/main/main.scss"; -@import "./components/search_page/search.scss"; -@import "./components/integrations/integrations.scss"; - -// TODO: Cleanup everything above this line - -@import "./style/variables"; -@import "./style/utilities"; -@import "./style/buttons"; -@import "./style/layout"; -@import "./style/sidebar"; -@import "./style/markdown"; -@import "./style/shortcuts"; -@import "./style/monaco"; -@import "./style/filetree"; -@import "./style/query_bar"; -@import "./style/commits"; -@import "./style/filters"; diff --git a/x-pack/legacy/plugins/code/public/index.ts b/x-pack/legacy/plugins/code/public/index.ts deleted file mode 100644 index ea760c670cec4..0000000000000 --- a/x-pack/legacy/plugins/code/public/index.ts +++ /dev/null @@ -1,19 +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 { PluginInitializerContext } from 'src/core/public'; -import { npStart } from 'ui/new_platform'; -import { Plugin } from './plugin'; - -export function plugin(initializerContext: PluginInitializerContext) { - return new Plugin(initializerContext); -} - -export * from './shared'; - -// This is the shim to legacy platform -const p = plugin({} as PluginInitializerContext); -p.start(npStart.core); diff --git a/x-pack/legacy/plugins/code/public/lib/documentation_links.ts b/x-pack/legacy/plugins/code/public/lib/documentation_links.ts deleted file mode 100644 index 32561b0d19833..0000000000000 --- a/x-pack/legacy/plugins/code/public/lib/documentation_links.ts +++ /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 { npStart } from 'ui/new_platform'; - -// TODO make sure document links are right -export const documentationLinks = { - code: `${npStart.core.docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${npStart.core.docLinks.DOC_LINK_VERSION}/code.html`, - codeIntelligence: `${npStart.core.docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${npStart.core.docLinks.DOC_LINK_VERSION}/code.html`, - gitFormat: `${npStart.core.docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${npStart.core.docLinks.DOC_LINK_VERSION}/code.html`, - codeInstallLangServer: `${npStart.core.docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${npStart.core.docLinks.DOC_LINK_VERSION}/code-install-lang-server.html`, - codeGettingStarted: `${npStart.core.docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${npStart.core.docLinks.DOC_LINK_VERSION}/code-import-first-repo.html`, - codeRepoManagement: `${npStart.core.docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${npStart.core.docLinks.DOC_LINK_VERSION}/code-repo-management.html`, - codeSearch: `${npStart.core.docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${npStart.core.docLinks.DOC_LINK_VERSION}/code-search.html`, - codeOtherFeatures: `${npStart.core.docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${npStart.core.docLinks.DOC_LINK_VERSION}/code-basic-nav.html`, - semanticNavigation: `${npStart.core.docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${npStart.core.docLinks.DOC_LINK_VERSION}/code-semantic-nav.html`, - kibanaRoleManagement: `${npStart.core.docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${npStart.core.docLinks.DOC_LINK_VERSION}/kibana-role-management.html`, -}; diff --git a/x-pack/legacy/plugins/code/public/lib/usage_collector.ts b/x-pack/legacy/plugins/code/public/lib/usage_collector.ts deleted file mode 100644 index 5b63227ee4655..0000000000000 --- a/x-pack/legacy/plugins/code/public/lib/usage_collector.ts +++ /dev/null @@ -1,14 +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 { - createUiStatsReporter, - METRIC_TYPE, -} from '../../../../../../src/legacy/core_plugins/ui_metric/public'; -import { APP_USAGE_TYPE } from '../../common/constants'; - -export const trackUiAction = createUiStatsReporter(APP_USAGE_TYPE); -export { METRIC_TYPE }; diff --git a/x-pack/legacy/plugins/code/public/monaco/blame/blame_widget.ts b/x-pack/legacy/plugins/code/public/monaco/blame/blame_widget.ts deleted file mode 100644 index 3ef2afad64370..0000000000000 --- a/x-pack/legacy/plugins/code/public/monaco/blame/blame_widget.ts +++ /dev/null @@ -1,75 +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 { editor as Editor } from 'monaco-editor'; -import React from 'react'; -import ReactDOM from 'react-dom'; -import { GitBlame } from '../../../common/git_blame'; -import { Blame } from '../../components/main/blame'; - -export class BlameWidget implements Editor.IContentWidget { - public allowEditorOverflow = true; - - public suppressMouseDown = false; - private domNode: HTMLDivElement; - private containerNode: HTMLDivElement; - - constructor( - public readonly blame: GitBlame, - public readonly isFirstLine: boolean, - public readonly editor: Editor.IStandaloneCodeEditor - ) { - this.containerNode = document.createElement('div'); - this.domNode = document.createElement('div'); - this.containerNode.appendChild(this.domNode); - this.editor.onDidLayoutChange(() => this.update()); - // this.editor.onDidScrollChange(e => this.update()); - this.update(); - // @ts-ignore - this.editor.addContentWidget(this); - this.editor.layoutContentWidget(this); - } - - public destroy() { - this.editor.removeContentWidget(this); - } - - public getDomNode(): HTMLElement { - return this.containerNode; - } - - public getId(): string { - return 'blame_' + this.blame.startLine; - } - - public getPosition(): Editor.IContentWidgetPosition { - return { - position: { - column: 0, - lineNumber: this.blame.startLine, - }, - preference: [0], - }; - } - - private update() { - this.containerNode.style.width = '0px'; - const { fontSize, lineHeight } = this.editor.getConfiguration().fontInfo; - this.domNode.style.position = 'relative'; - this.domNode.style.left = '-332px'; - this.domNode.style.marginLeft = '16px'; - this.domNode.style.width = '300px'; - this.domNode.style.fontSize = `${fontSize}px`; - this.domNode.style.lineHeight = `${lineHeight}px`; - const element = React.createElement( - Blame, - { blame: this.blame, isFirstLine: this.isFirstLine }, - null - ); - // @ts-ignore - ReactDOM.render(element, this.domNode); - } -} diff --git a/x-pack/legacy/plugins/code/public/monaco/computer.ts b/x-pack/legacy/plugins/code/public/monaco/computer.ts deleted file mode 100644 index ef26b16849ba6..0000000000000 --- a/x-pack/legacy/plugins/code/public/monaco/computer.ts +++ /dev/null @@ -1,14 +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 interface AsyncTask { - promise(): Promise; - cancel(): void; -} -export interface Computer { - compute(): AsyncTask; - loadingMessage(): T; -} diff --git a/x-pack/legacy/plugins/code/public/monaco/content_widget.ts b/x-pack/legacy/plugins/code/public/monaco/content_widget.ts deleted file mode 100644 index baa75de956a61..0000000000000 --- a/x-pack/legacy/plugins/code/public/monaco/content_widget.ts +++ /dev/null @@ -1,140 +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 { editor as Editor } from 'monaco-editor'; -// @ts-ignore -import { DomScrollableElement } from 'monaco-editor/esm/vs/base/browser/ui/scrollbar/scrollableElement'; -import { Disposable } from './disposable'; -import { monaco } from './monaco'; - -export function toggleClass(node: HTMLElement, clazzName: string, toggle: boolean) { - node.classList.toggle(clazzName, toggle); -} - -export abstract class ContentWidget extends Disposable implements Editor.IContentWidget { - protected get isVisible(): boolean { - return this.visible; - } - - protected set isVisible(value: boolean) { - this.visible = value; - toggleClass(this.containerDomNode, 'hidden', !this.visible); - } - protected readonly containerDomNode: HTMLElement; - protected domNode: HTMLElement; - private readonly extraNode: HTMLDivElement; - private scrollbar: any; - private showAtPosition: Position | null; - private stoleFocus: boolean = false; - private visible: boolean; - - protected constructor(public readonly id: string, public readonly editor: Editor.ICodeEditor) { - super(); - this.containerDomNode = document.createElement('div'); - this.domNode = document.createElement('div'); - this.extraNode = document.createElement('div'); - this.scrollbar = new DomScrollableElement(this.domNode, {}); - this.disposables.push(this.scrollbar); - this.containerDomNode.appendChild(this.scrollbar.getDomNode()); - this.containerDomNode.appendChild(this.extraNode); - - this.visible = false; - this.editor.onDidLayoutChange(e => this.updateMaxHeight()); - this.editor.onDidChangeModel(() => this.hide()); - this.updateMaxHeight(); - this.showAtPosition = null; - // @ts-ignore - this.editor.addContentWidget(this); - } - - public getId(): string { - return this.id; - } - - public getDomNode(): HTMLElement { - return this.containerDomNode; - } - - public showAt(position: any, focus: boolean): void { - this.showAtPosition = position; - // @ts-ignore - this.editor.layoutContentWidget(this); - this.isVisible = true; - this.editor.render(); - this.stoleFocus = focus; - if (focus) { - this.containerDomNode.focus(); - } - } - - public hide(): void { - if (!this.isVisible) { - return; - } - - this.isVisible = false; - // @ts-ignore - this.editor.layoutContentWidget(this); - if (this.stoleFocus) { - this.editor.focus(); - } - } - - // @ts-ignore - public getPosition() { - const { ContentWidgetPositionPreference } = monaco.editor; - if (this.isVisible) { - return { - position: this.showAtPosition!, - preference: [ContentWidgetPositionPreference.ABOVE, ContentWidgetPositionPreference.BELOW], - }; - } - return null; - } - - public dispose(): void { - // @ts-ignore - this.editor.removeContentWidget(this); - this.disposables.forEach(d => d.dispose()); - } - - protected updateContents(node: Node, extra?: Node): void { - this.domNode.textContent = ''; - this.domNode.appendChild(node); - this.extraNode.innerHTML = ''; - if (extra) { - this.extraNode.appendChild(extra); - } - this.updateFont(); - // @ts-ignore - this.editor.layoutContentWidget(this); - this.onContentsChange(); - } - - protected onContentsChange(): void { - this.scrollbar.scanDomNode(); - } - - private updateMaxHeight() { - const height = Math.max(this.editor.getLayoutInfo().height / 4, 250); - const { fontSize, lineHeight } = this.editor.getConfiguration().fontInfo; - - this.domNode.style.fontSize = `${fontSize}px`; - this.domNode.style.lineHeight = `${lineHeight}px`; - this.domNode.style.maxHeight = `${height}px`; - } - - private updateFont(): void { - const codeTags: HTMLElement[] = Array.prototype.slice.call( - this.domNode.getElementsByTagName('code') - ); - const codeClasses: HTMLElement[] = Array.prototype.slice.call( - this.domNode.getElementsByClassName('code') - ); - - [...codeTags, ...codeClasses].forEach(node => this.editor.applyFontInfo(node)); - } -} diff --git a/x-pack/legacy/plugins/code/public/monaco/definition/definition_provider.ts b/x-pack/legacy/plugins/code/public/monaco/definition/definition_provider.ts deleted file mode 100644 index 929e9d0737af3..0000000000000 --- a/x-pack/legacy/plugins/code/public/monaco/definition/definition_provider.ts +++ /dev/null @@ -1,88 +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 url from 'url'; -import queryString from 'querystring'; -import { DetailSymbolInformation } from '@elastic/lsp-extension'; - -import { npStart } from 'ui/new_platform'; -import { Location } from 'vscode-languageserver-types'; -import { monaco } from '../monaco'; -import { LspRestClient, TextDocumentMethods } from '../../../common/lsp_client'; -import { parseSchema } from '../../../common/uri_util'; -import { history } from '../../utils/url'; - -export const definitionProvider: monaco.languages.DefinitionProvider = { - async provideDefinition( - model: monaco.editor.ITextModel, - position: monaco.Position, - token: monaco.CancellationToken - ): Promise { - const lspClient = new LspRestClient('/api/code/lsp'); - const lspMethods = new TextDocumentMethods(lspClient); - function handleLocation(location: Location): monaco.languages.Location { - return { - uri: monaco.Uri.parse(location.uri), - range: { - startLineNumber: location.range.start.line + 1, - startColumn: location.range.start.character + 1, - endLineNumber: location.range.end.line + 1, - endColumn: location.range.end.character + 1, - }, - }; - } - - async function handleQname(qname: string): Promise { - const res = await npStart.core.http.get(`/api/code/lsp/symbol/${qname}`); - if (res.symbols) { - return res.symbols.map((s: DetailSymbolInformation) => - handleLocation(s.symbolInformation.location) - ); - } - return []; - } - - function openDefinitionsPanel() { - if (model && position) { - const { uri } = parseSchema(model.uri.toString()); - const refUrl = `git:/${uri}!L${position.lineNumber - 1}:${position.column - 1}`; - const queries = url.parse(history.location.search, true).query; - const query = queryString.stringify({ - ...queries, - tab: 'definitions', - refUrl, - }); - history.push(`${uri}?${query}`); - } - } - - const result = await lspMethods.edefinition.send({ - position: { - line: position.lineNumber - 1, - character: position.column - 1, - }, - textDocument: { - uri: model.uri.toString(), - }, - }); - - if (result) { - if (result.length > 1) { - openDefinitionsPanel(); - return result.filter(l => l.location !== undefined).map(l => handleLocation(l.location!)); - } else { - const l = result[0]; - const location = l.location; - if (location) { - return [handleLocation(location)]; - } else if (l.qname) { - return await handleQname(l.qname); - } - } - } - return []; - }, -}; diff --git a/x-pack/legacy/plugins/code/public/monaco/disposable.ts b/x-pack/legacy/plugins/code/public/monaco/disposable.ts deleted file mode 100644 index d89048357234e..0000000000000 --- a/x-pack/legacy/plugins/code/public/monaco/disposable.ts +++ /dev/null @@ -1,25 +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 { IDisposable } from 'monaco-editor'; - -export abstract class Disposable implements IDisposable { - protected disposables: IDisposable[]; - - constructor() { - this.disposables = []; - } - - public dispose(): void { - this.disposables.forEach(d => d.dispose()); - this.disposables = []; - } - - protected _register(t: T): T { - this.disposables.push(t); - return t; - } -} diff --git a/x-pack/legacy/plugins/code/public/monaco/editor_service.ts b/x-pack/legacy/plugins/code/public/monaco/editor_service.ts deleted file mode 100644 index 2b3b5513a39a4..0000000000000 --- a/x-pack/legacy/plugins/code/public/monaco/editor_service.ts +++ /dev/null @@ -1,75 +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 { editor, IRange, Uri } from 'monaco-editor'; -// @ts-ignore -import { StandaloneCodeEditorServiceImpl } from 'monaco-editor/esm/vs/editor/standalone/browser/standaloneCodeServiceImpl.js'; -import { npStart } from 'ui/new_platform'; -import { parseSchema } from '../../common/uri_util'; -import { SymbolSearchResult } from '../../model'; -import { history } from '../utils/url'; -import { MonacoHelper } from './monaco_helper'; -import { ResponseError } from '../../common/jsonrpc'; -interface IResourceInput { - resource: Uri; - options?: { selection?: IRange }; -} - -export class EditorService extends StandaloneCodeEditorServiceImpl { - constructor(private readonly getUrlQuery: () => string) { - super(); - } - public static async handleSymbolUri(qname: string, getUrlQuery: () => string) { - const result = await EditorService.findSymbolByQname(qname); - if (result.symbols.length > 0) { - const symbol = result.symbols[0].symbolInformation; - const { schema, uri } = parseSchema(symbol.location.uri); - if (schema === 'git:') { - const { line, character } = symbol.location.range.start; - const url = uri + `!L${line + 1}:${character + 1}`; - history.push(`${url}${getUrlQuery()}`); - } - } - } - - public static async findSymbolByQname(qname: string) { - try { - const response = await npStart.core.http.get(`/api/code/lsp/symbol/${qname}`); - return response as SymbolSearchResult; - } catch (e) { - const error = e.body; - throw new ResponseError(error.code, error.message, error.data); - } - } - private helper?: MonacoHelper; - public async openCodeEditor( - input: IResourceInput, - source: editor.ICodeEditor, - sideBySide?: boolean - ) { - const { scheme, authority, path } = input.resource; - if (scheme === 'symbol') { - await EditorService.handleSymbolUri(authority, this.getUrlQuery); - } else { - const uri = `/${authority}${path}`; - if (input.options && input.options.selection) { - const { startColumn, startLineNumber } = input.options.selection; - const url = uri + `!L${startLineNumber}:${startColumn}`; - const currentPath = window.location.hash.substring(1); - if (currentPath === url) { - this.helper!.revealPosition(startLineNumber, startColumn); - } else { - history.push(`${url}${this.getUrlQuery()}`); - } - } - } - return source; - } - - public setMonacoHelper(helper: MonacoHelper) { - this.helper = helper; - } -} diff --git a/x-pack/legacy/plugins/code/public/monaco/hover/content_hover_widget.tsx b/x-pack/legacy/plugins/code/public/monaco/hover/content_hover_widget.tsx deleted file mode 100644 index 99556fa1d2b77..0000000000000 --- a/x-pack/legacy/plugins/code/public/monaco/hover/content_hover_widget.tsx +++ /dev/null @@ -1,218 +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 { I18nProvider } from '@kbn/i18n/react'; -import { editor as Editor, languages, Range as EditorRange } from 'monaco-editor'; -// @ts-ignore -import { createCancelablePromise } from 'monaco-editor/esm/vs/base/common/async'; -// @ts-ignore -import { getOccurrencesAtPosition } from 'monaco-editor/esm/vs/editor/contrib/wordHighlighter/wordHighlighter'; - -import React from 'react'; -import ReactDOM from 'react-dom'; -import { Hover, MarkedString, Range } from 'vscode-languageserver-types'; -import { ServerNotInitialized } from '../../../common/lsp_error_codes'; -import { HoverButtons } from '../../components/hover/hover_buttons'; -import { HoverState, HoverWidget, HoverWidgetProps } from '../../components/hover/hover_widget'; -import { ContentWidget } from '../content_widget'; -import { monaco } from '../monaco'; -import { Operation } from '../operation'; -import { HoverComputer } from './hover_computer'; - -export class ContentHoverWidget extends ContentWidget { - public static ID = 'editor.contrib.contentHoverWidget'; - private static readonly DECORATION_OPTIONS = { - className: 'wordHighlightStrong', // hoverHighlight wordHighlightStrong - }; - private hoverOperation: Operation; - private readonly computer: HoverComputer; - private lastRange: EditorRange | null = null; - private shouldFocus: boolean = false; - private hoverResultAction?: (hover: Hover) => void = undefined; - private highlightDecorations: string[] = []; - private hoverState: HoverState = HoverState.LOADING; - - constructor(editor: Editor.ICodeEditor) { - super(ContentHoverWidget.ID, editor); - this.containerDomNode.className = 'monaco-editor-hover hidden'; - this.containerDomNode.tabIndex = 0; - this.domNode.className = 'monaco-editor-hover-content'; - this.computer = new HoverComputer(); - this.hoverOperation = new Operation( - this.computer, - result => this.result(result), - error => { - // @ts-ignore - if (error.code === ServerNotInitialized) { - this.hoverState = HoverState.INITIALIZING; - this.render(this.lastRange!); - } - }, - () => { - this.hoverState = HoverState.LOADING; - this.render(this.lastRange!); - } - ); - } - - public startShowingAt(range: any, focus: boolean) { - if (this.isVisible && this.lastRange && this.lastRange.containsRange(range)) { - return; - } - this.hoverOperation.cancel(); - const url = this.editor.getModel()!.uri.toString(); - if (this.isVisible) { - this.hide(); - } - this.computer.setParams(url, range); - this.hoverOperation.start(); - this.lastRange = range; - this.shouldFocus = focus; - } - - public setHoverResultAction(hoverResultAction: (hover: Hover) => void) { - this.hoverResultAction = hoverResultAction; - } - - public hide(): void { - super.hide(); - this.highlightDecorations = this.editor.deltaDecorations(this.highlightDecorations, []); - } - - private result(result: Hover) { - if (this.hoverResultAction) { - // pass the result to redux - this.hoverResultAction(result); - } - if (this.lastRange && result && result.contents) { - this.render(this.lastRange, result); - } else { - this.hide(); - } - } - - private render(renderRange: EditorRange, result?: Hover) { - const fragment = document.createDocumentFragment(); - let props: HoverWidgetProps = { - state: this.hoverState, - gotoDefinition: this.gotoDefinition.bind(this), - findReferences: this.findReferences.bind(this), - }; - let startColumn = renderRange.startColumn; - if (result) { - let contents: MarkedString[] = []; - if (Array.isArray(result.contents)) { - contents = result.contents; - } else { - contents = [result.contents as MarkedString]; - } - contents = contents.filter(v => { - if (typeof v === 'string') { - return !!v; - } else { - return !!v.value; - } - }); - if (contents.length === 0) { - this.hide(); - return; - } - props = { - ...props, - state: HoverState.READY, - contents, - }; - if (result.range) { - this.lastRange = this.toMonacoRange(result.range); - this.highlightOccurrences(this.lastRange); - } - startColumn = Math.min( - renderRange.startColumn, - result.range ? result.range.start.character + 1 : Number.MAX_VALUE - ); - } - - this.showAt(new monaco.Position(renderRange.startLineNumber, startColumn), this.shouldFocus); - - const element = ( - - - - ); - // @ts-ignore - ReactDOM.render(element, fragment); - const buttonFragment = document.createDocumentFragment(); - const buttons = ( - - - - ); - // @ts-ignore - ReactDOM.render(buttons, buttonFragment); - this.updateContents(fragment, buttonFragment); - } - - private toMonacoRange(r: Range): EditorRange { - return new monaco.Range( - r.start.line + 1, - r.start.character + 1, - r.end.line + 1, - r.end.character + 1 - ); - } - - private gotoDefinition() { - if (this.lastRange) { - this.editor.setPosition({ - lineNumber: this.lastRange.startLineNumber, - column: this.lastRange.startColumn, - }); - const action = this.editor.getAction('editor.action.revealDefinition'); - action.run().then(() => this.hide()); - } - } - - private findReferences() { - if (this.lastRange) { - this.editor.setPosition({ - lineNumber: this.lastRange.startLineNumber, - column: this.lastRange.startColumn, - }); - const action = this.editor.getAction('editor.action.referenceSearch.trigger'); - action.run().then(() => this.hide()); - } - } - - private highlightOccurrences(range: EditorRange) { - const pos = new monaco.Position(range.startLineNumber, range.startColumn); - return createCancelablePromise((token: any) => - getOccurrencesAtPosition(this.editor.getModel(), pos, token).then( - (data: languages.DocumentHighlight[]) => { - if (data) { - if (this.isVisible) { - const decorations = data.map(h => ({ - range: h.range, - options: ContentHoverWidget.DECORATION_OPTIONS, - })); - - this.highlightDecorations = this.editor.deltaDecorations( - this.highlightDecorations, - decorations - ); - } - } else { - this.highlightDecorations = this.editor.deltaDecorations(this.highlightDecorations, [ - { - range, - options: ContentHoverWidget.DECORATION_OPTIONS, - }, - ]); - } - } - ) - ); - } -} diff --git a/x-pack/legacy/plugins/code/public/monaco/hover/hover_computer.ts b/x-pack/legacy/plugins/code/public/monaco/hover/hover_computer.ts deleted file mode 100644 index 7e49b5f027411..0000000000000 --- a/x-pack/legacy/plugins/code/public/monaco/hover/hover_computer.ts +++ /dev/null @@ -1,98 +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 { Hover } from 'vscode-languageserver-types'; -import { LspRestClient, TextDocumentMethods } from '../../../common/lsp_client'; -import { AsyncTask, Computer } from '../computer'; -import { store } from '../../stores/index'; -import { statusSelector } from '../../selectors'; -import { LangServerType, RepoFileStatus } from '../../../common/repo_file_status'; -import { ServerNotInitialized } from '../../../common/lsp_error_codes'; - -export const LOADING = 'loading'; - -export class HoverComputer implements Computer { - private lspMethods: TextDocumentMethods; - private range: any = null; - private uri: string | null = null; - - constructor() { - const lspClient = new LspRestClient('/api/code/lsp'); - this.lspMethods = new TextDocumentMethods(lspClient); - } - - public setParams(uri: string, range: any) { - this.range = range; - this.uri = uri; - } - - public compute(): AsyncTask { - const status = statusSelector(store.getState()); - if ( - status && - status.langServerType !== LangServerType.NONE && - status.fileStatus !== RepoFileStatus.FILE_NOT_SUPPORTED && - status.fileStatus !== RepoFileStatus.FILE_IS_TOO_BIG - ) { - if (status.langServerStatus === RepoFileStatus.LANG_SERVER_IS_INITIALIZING) { - return this.initializing(); - } - - return this.lspMethods.hover.asyncTask({ - position: { - line: this.range!.startLineNumber - 1, - character: this.range!.startColumn - 1, - }, - textDocument: { - uri: this.uri!, - }, - }); - } - return this.empty(); - } - - private empty(): AsyncTask { - const empty = { - range: this.lspRange(), - contents: [], - }; - return { - cancel(): void {}, - promise(): Promise { - return Promise.resolve(empty); - }, - }; - } - - private initializing(): AsyncTask { - return { - cancel(): void {}, - promise(): Promise { - return Promise.reject({ code: ServerNotInitialized }); - }, - }; - } - - private lspRange() { - return { - start: { - line: this.range.startLineNumber - 1, - character: this.range.startColumn - 1, - }, - end: { - line: this.range.endLineNumber - 1, - character: this.range.endColumn - 1, - }, - }; - } - - public loadingMessage(): Hover { - return { - range: this.lspRange(), - contents: LOADING, - }; - } -} diff --git a/x-pack/legacy/plugins/code/public/monaco/hover/hover_controller.ts b/x-pack/legacy/plugins/code/public/monaco/hover/hover_controller.ts deleted file mode 100644 index 81382774791ac..0000000000000 --- a/x-pack/legacy/plugins/code/public/monaco/hover/hover_controller.ts +++ /dev/null @@ -1,64 +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 { editor as Editor, IDisposable, IKeyboardEvent } from 'monaco-editor'; -import { EditorActions } from '../../components/editor/editor'; -import { monaco } from '../monaco'; -import { ContentHoverWidget } from './content_hover_widget'; - -export class HoverController implements Editor.IEditorContribution { - public static ID = 'code.editor.contrib.hover'; - public static get(editor: any): HoverController { - return editor.getContribution(HoverController.ID); - } - private contentWidget: ContentHoverWidget; - private disposables: IDisposable[]; - - constructor(public readonly editor: Editor.ICodeEditor) { - this.disposables = [ - this.editor.onMouseMove((e: Editor.IEditorMouseEvent) => this.onEditorMouseMove(e)), - this.editor.onKeyDown((e: IKeyboardEvent) => this.onKeyDown(e)), - ]; - this.contentWidget = new ContentHoverWidget(editor); - } - - public dispose(): void { - this.disposables.forEach(d => d.dispose()); - } - - public getId(): string { - return HoverController.ID; - } - - public setReduxActions(actions: EditorActions) { - this.contentWidget.setHoverResultAction(actions.hoverResult); - } - - private onEditorMouseMove(mouseEvent: Editor.IEditorMouseEvent) { - const targetType = mouseEvent.target.type; - const { MouseTargetType } = monaco.editor; - - if ( - targetType === MouseTargetType.CONTENT_WIDGET && - mouseEvent.target.detail === ContentHoverWidget.ID - ) { - return; - } - - if (targetType === MouseTargetType.CONTENT_TEXT) { - this.contentWidget.startShowingAt(mouseEvent.target.range, false); - } else { - this.contentWidget.hide(); - } - } - - private onKeyDown(e: IKeyboardEvent): void { - if (e.keyCode === monaco.KeyCode.Escape) { - // Do not hide hover when Ctrl/Meta is pressed - this.contentWidget.hide(); - } - } -} diff --git a/x-pack/legacy/plugins/code/public/monaco/immortal_reference.ts b/x-pack/legacy/plugins/code/public/monaco/immortal_reference.ts deleted file mode 100644 index b1ec674c1ee9c..0000000000000 --- a/x-pack/legacy/plugins/code/public/monaco/immortal_reference.ts +++ /dev/null @@ -1,14 +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 { IReference } from './textmodel_resolver'; - -export class ImmortalReference implements IReference { - constructor(public object: T) {} - public dispose(): void { - /* noop */ - } -} diff --git a/x-pack/legacy/plugins/code/public/monaco/monaco.ts b/x-pack/legacy/plugins/code/public/monaco/monaco.ts deleted file mode 100644 index 06b55e4d74c57..0000000000000 --- a/x-pack/legacy/plugins/code/public/monaco/monaco.ts +++ /dev/null @@ -1,217 +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. - */ -// (1) Desired editor features: -import 'monaco-editor/esm/vs/editor/browser/controller/coreCommands.js'; -import 'monaco-editor/esm/vs/editor/browser/widget/codeEditorWidget.js'; -import 'monaco-editor/esm/vs/editor/browser/widget/diffEditorWidget.js'; -// import 'monaco-editor/esm/vs/editor/browser/widget/diffNavigator.js'; -// import 'monaco-editor/esm/vs/editor/contrib/bracketMatching/bracketMatching.js'; -// import 'monaco-editor/esm/vs/editor/contrib/caretOperations/caretOperations.js'; -// import 'monaco-editor/esm/vs/editor/contrib/caretOperations/transpose.js'; -import 'monaco-editor/esm/vs/editor/contrib/clipboard/clipboard.js'; -// import 'monaco-editor/esm/vs/editor/contrib/codelens/codelensController.js'; -// import 'monaco-editor/esm/vs/editor/contrib/colorPicker/colorDetector.js'; -// import 'monaco-editor/esm/vs/editor/contrib/comment/comment.js'; -// import 'monaco-editor/esm/vs/editor/contrib/contextmenu/contextmenu.js'; -// import 'monaco-editor/esm/vs/editor/contrib/cursorUndo/cursorUndo.js'; -// import 'monaco-editor/esm/vs/editor/contrib/dnd/dnd.js'; -import 'monaco-editor/esm/vs/editor/contrib/find/findController.js'; -import 'monaco-editor/esm/vs/editor/contrib/folding/folding.js'; -// import 'monaco-editor/esm/vs/editor/contrib/format/formatActions.js'; -import 'monaco-editor/esm/vs/editor/contrib/goToDefinition/goToDefinitionCommands'; -import 'monaco-editor/esm/vs/editor/contrib/goToDefinition/goToDefinitionMouse'; -// import 'monaco-editor/esm/vs/editor/contrib/gotoError/gotoError.js'; -// import 'monaco-editor/esm/vs/editor/contrib/hover/hover.js'; -// import 'monaco-editor/esm/vs/editor/contrib/inPlaceReplace/inPlaceReplace.js'; -// import 'monaco-editor/esm/vs/editor/contrib/linesOperations/linesOperations.js'; -// import 'monaco-editor/esm/vs/editor/contrib/links/links.js'; -// import 'monaco-editor/esm/vs/editor/contrib/multicursor/multicursor.js'; -// import 'monaco-editor/esm/vs/editor/contrib/parameterHints/parameterHints.js'; -// import 'monaco-editor/esm/vs/editor/contrib/quickFix/quickFixCommands.js'; -// import 'monaco-editor/esm/vs/editor/contrib/referenceSearch/referenceSearch.js'; -// import 'monaco-editor/esm/vs/editor/contrib/rename/rename.js'; -// import 'monaco-editor/esm/vs/editor/contrib/smartSelect/smartSelect.js'; -// import 'monaco-editor/esm/vs/editor/contrib/snippet/snippetController2.js'; -// import 'monaco-editor/esm/vs/editor/contrib/suggest/suggestController.js'; -// import 'monaco-editor/esm/vs/editor/contrib/toggleTabFocusMode/toggleTabFocusMode.js'; -// import 'monaco-editor/esm/vs/editor/contrib/wordHighlighter/wordHighlighter.js'; -// import 'monaco-editor/esm/vs/editor/contrib/wordOperations/wordOperations.js'; -// import 'monaco-editor/esm/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.js'; -// import 'monaco-editor/esm/vs/editor/standalone/browser/inspectTokens/inspectTokens.js'; -// import 'monaco-editor/esm/vs/editor/standalone/browser/iPadShowKeyboard/iPadShowKeyboard.js'; -// import 'monaco-editor/esm/vs/editor/standalone/browser/quickOpen/quickOutline.js'; -// import 'monaco-editor/esm/vs/editor/standalone/browser/quickOpen/gotoLine.js'; -// import 'monaco-editor/esm/vs/editor/standalone/browser/quickOpen/quickCommand.js'; -// import 'monaco-editor/esm/vs/editor/standalone/browser/toggleHighContrast/toggleHighContrast.js'; -import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js'; - -import darkTheme from '@elastic/eui/dist/eui_theme_dark.json'; -import lightTheme from '@elastic/eui/dist/eui_theme_light.json'; -// (2) Desired languages: -// import 'monaco-editor/esm/vs/language/typescript/monaco.contribution'; -// import 'monaco-editor/esm/vs/language/css/monaco.contribution'; -// import 'monaco-editor/esm/vs/language/json/monaco.contribution'; -// import 'monaco-editor/esm/vs/language/html/monaco.contribution'; -// import 'monaco-editor/esm/vs/basic-languages/bat/bat.contribution.js'; -import 'monaco-editor/esm/vs/basic-languages/coffee/coffee.contribution.js'; -import 'monaco-editor/esm/vs/basic-languages/cpp/cpp.contribution.js'; -import 'monaco-editor/esm/vs/basic-languages/csharp/csharp.contribution.js'; -// import 'monaco-editor/esm/vs/basic-languages/csp/csp.contribution.js'; -import 'monaco-editor/esm/vs/basic-languages/css/css.contribution.js'; -// import 'monaco-editor/esm/vs/basic-languages/dockerfile/dockerfile.contribution.js'; -import 'monaco-editor/esm/vs/basic-languages/fsharp/fsharp.contribution.js'; -import 'monaco-editor/esm/vs/basic-languages/go/go.contribution.js'; -// import 'monaco-editor/esm/vs/basic-languages/handlebars/handlebars.contribution.js'; -import 'monaco-editor/esm/vs/basic-languages/html/html.contribution.js'; -// import 'monaco-editor/esm/vs/basic-languages/ini/ini.contribution.js'; -import 'monaco-editor/esm/vs/basic-languages/java/java.contribution.js'; -import 'monaco-editor/esm/vs/basic-languages/r/r.contribution.js'; -// import 'monaco-editor/esm/vs/basic-languages/razor/razor.contribution.js'; -// import 'monaco-editor/esm/vs/basic-languages/redis/redis.contribution.js'; -// import 'monaco-editor/esm/vs/basic-languages/redshift/redshift.contribution.js'; -import 'monaco-editor/esm/vs/basic-languages/ruby/ruby.contribution.js'; -// import 'monaco-editor/esm/vs/basic-languages/sb/sb.contribution.js'; -import 'monaco-editor/esm/vs/basic-languages/scss/scss.contribution.js'; -import 'monaco-editor/esm/vs/basic-languages/solidity/solidity.contribution.js'; -import 'monaco-editor/esm/vs/basic-languages/sql/sql.contribution.js'; -import 'monaco-editor/esm/vs/basic-languages/swift/swift.contribution.js'; -import 'monaco-editor/esm/vs/basic-languages/vb/vb.contribution.js'; -import 'monaco-editor/esm/vs/basic-languages/xml/xml.contribution.js'; -import 'monaco-editor/esm/vs/basic-languages/yaml/yaml.contribution.js'; -import 'monaco-editor/esm/vs/basic-languages/javascript/javascript.contribution'; -import 'monaco-editor/esm/vs/basic-languages/less/less.contribution.js'; -import 'monaco-editor/esm/vs/basic-languages/lua/lua.contribution.js'; -// import 'monaco-editor/esm/vs/basic-languages/markdown/markdown.contribution.js'; -// import 'monaco-editor/esm/vs/basic-languages/msdax/msdax.contribution.js'; -import 'monaco-editor/esm/vs/basic-languages/mysql/mysql.contribution.js'; -import 'monaco-editor/esm/vs/basic-languages/objective-c/objective-c.contribution.js'; -import 'monaco-editor/esm/vs/basic-languages/pgsql/pgsql.contribution.js'; -import 'monaco-editor/esm/vs/basic-languages/php/php.contribution.js'; -// import 'monaco-editor/esm/vs/basic-languages/postiats/postiats.contribution.js'; -import 'monaco-editor/esm/vs/basic-languages/powershell/powershell.contribution.js'; -// import 'monaco-editor/esm/vs/basic-languages/pug/pug.contribution.js'; -import 'monaco-editor/esm/vs/basic-languages/python/python.contribution.js'; -import 'monaco-editor/esm/vs/basic-languages/typescript/typescript.contribution'; -import chrome from 'ui/chrome'; -import { npStart } from 'ui/new_platform'; - -import { CTAGS_SUPPORT_LANGS } from '../../common/language_server'; -import { definitionProvider } from './definition/definition_provider'; - -const IS_DARK_THEME = npStart.core.uiSettings.get('theme:darkMode'); - -const themeName = IS_DARK_THEME ? darkTheme : lightTheme; - -const syntaxTheme = { - keyword: themeName.euiColorAccent, - comment: themeName.euiColorDarkShade, - delimiter: themeName.euiColorSecondary, - string: themeName.euiColorPrimary, - number: themeName.euiColorWarning, - regexp: themeName.euiColorPrimary, - types: `${IS_DARK_THEME ? themeName.euiColorVis5 : themeName.euiColorVis9}`, - annotation: themeName.euiColorLightShade, - tag: themeName.euiColorAccent, - symbol: themeName.euiColorDanger, - foreground: themeName.euiColorDarkestShade, - editorBackground: themeName.euiColorEmptyShade, - lineNumbers: themeName.euiColorDarkShade, - editorIndentGuide: themeName.euiColorLightShade, - selectionBackground: `${IS_DARK_THEME ? '#343551' : '#E3E4ED'}`, - editorWidgetBackground: themeName.euiColorLightestShade, - editorWidgetBorder: themeName.euiColorLightShade, - findMatchBackground: themeName.euiColorWarning, - findMatchHighlightBackground: themeName.euiColorWarning, -}; - -monaco.editor.defineTheme('euiColors', { - base: 'vs', - inherit: true, - rules: [ - { - token: '', - foreground: themeName.euiColorDarkestShade, - background: themeName.euiColorEmptyShade, - }, - { token: 'invalid', foreground: themeName.euiColorAccent }, - { token: 'emphasis', fontStyle: 'italic' }, - { token: 'strong', fontStyle: 'bold' }, - - { token: 'variable', foreground: themeName.euiColorPrimary }, - { token: 'variable.predefined', foreground: themeName.euiColorSecondary }, - { token: 'constant', foreground: themeName.euiColorAccent }, - { token: 'comment', foreground: themeName.euiColorMediumShade }, - { token: 'number', foreground: themeName.euiColorWarning }, - { token: 'number.hex', foreground: themeName.euiColorPrimary }, - { token: 'regexp', foreground: themeName.euiColorDanger }, - { token: 'annotation', foreground: themeName.euiColorMediumShade }, - { token: 'type', foreground: themeName.euiColorVis0 }, - - { token: 'delimiter', foreground: themeName.euiColorDarkestShade }, - { token: 'delimiter.html', foreground: themeName.euiColorDarkShade }, - { token: 'delimiter.xml', foreground: themeName.euiColorPrimary }, - - { token: 'tag', foreground: themeName.euiColorDanger }, - { token: 'tag.id.jade', foreground: themeName.euiColorPrimary }, - { token: 'tag.class.jade', foreground: themeName.euiColorPrimary }, - { token: 'meta.scss', foreground: themeName.euiColorAccent }, - { token: 'metatag', foreground: themeName.euiColorSecondary }, - { token: 'metatag.content.html', foreground: themeName.euiColorDanger }, - { token: 'metatag.html', foreground: themeName.euiColorMediumShade }, - { token: 'metatag.xml', foreground: themeName.euiColorMediumShade }, - { token: 'metatag.php', fontStyle: 'bold' }, - - { token: 'key', foreground: themeName.euiColorWarning }, - { token: 'string.key.json', foreground: themeName.euiColorDanger }, - { token: 'string.value.json', foreground: themeName.euiColorPrimary }, - - { token: 'attribute.name', foreground: themeName.euiColorDanger }, - { token: 'attribute.name.css', foreground: themeName.euiColorSecondary }, - { token: 'attribute.value', foreground: themeName.euiColorPrimary }, - { token: 'attribute.value.number', foreground: themeName.euiColorWarning }, - { token: 'attribute.value.unit', foreground: themeName.euiColorWarning }, - { token: 'attribute.value.html', foreground: themeName.euiColorPrimary }, - { token: 'attribute.value.xml', foreground: themeName.euiColorPrimary }, - - { token: 'string', foreground: themeName.euiColorLightestShade }, - { token: 'string.html', foreground: themeName.euiColorPrimary }, - { token: 'string.sql', foreground: themeName.euiColorDanger }, - { token: 'string.yaml', foreground: themeName.euiColorPrimary }, - - { token: 'keyword', foreground: themeName.euiColorPrimary }, - { token: 'keyword.json', foreground: themeName.euiColorPrimary }, - { token: 'keyword.flow', foreground: themeName.euiColorWarning }, - { token: 'keyword.flow.scss', foreground: themeName.euiColorPrimary }, - - { token: 'operator.scss', foreground: themeName.euiColorDarkShade }, - { token: 'operator.sql', foreground: themeName.euiColorMediumShade }, - { token: 'operator.swift', foreground: themeName.euiColorMediumShade }, - { token: 'predefined.sql', foreground: themeName.euiColorMediumShade }, - ], - colors: { - 'editor.foreground': syntaxTheme.foreground, - 'editor.background': syntaxTheme.editorBackground, - 'editorLineNumber.foreground': syntaxTheme.lineNumbers, - 'editorLineNumber.activeForeground': syntaxTheme.lineNumbers, - 'editorIndentGuide.background': syntaxTheme.editorIndentGuide, - 'editor.selectionBackground': syntaxTheme.selectionBackground, - 'editorWidget.border': syntaxTheme.editorWidgetBorder, - 'editorWidget.background': syntaxTheme.editorWidgetBackground, - }, -}); -monaco.editor.setTheme('euiColors'); - -monaco.languages.registerDefinitionProvider('java', definitionProvider); -monaco.languages.registerDefinitionProvider('typescript', definitionProvider); -monaco.languages.registerDefinitionProvider('javascript', definitionProvider); -if (chrome.getInjected('enableLangserversDeveloping', false) === true) { - monaco.languages.registerDefinitionProvider('go', definitionProvider); -} -CTAGS_SUPPORT_LANGS.forEach(language => - monaco.languages.registerDefinitionProvider(language, definitionProvider) -); - -export { monaco }; diff --git a/x-pack/legacy/plugins/code/public/monaco/monaco_diff_editor.ts b/x-pack/legacy/plugins/code/public/monaco/monaco_diff_editor.ts deleted file mode 100644 index bd0daa5e5684c..0000000000000 --- a/x-pack/legacy/plugins/code/public/monaco/monaco_diff_editor.ts +++ /dev/null @@ -1,65 +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 { ResizeChecker } from '../components/shared/resize_checker'; -import { monaco } from './monaco'; -export class MonacoDiffEditor { - public diffEditor: monaco.editor.IStandaloneDiffEditor | null = null; - private resizeChecker: ResizeChecker | null = null; - constructor( - private readonly container: HTMLElement, - private readonly originCode: string, - private readonly modifiedCode: string, - private readonly language: string, - private readonly renderSideBySide: boolean - ) {} - - public init() { - return new Promise(resolve => { - const originalModel = monaco.editor.createModel(this.originCode, this.language); - const modifiedModel = monaco.editor.createModel(this.modifiedCode, this.language); - - const diffEditor = monaco.editor.createDiffEditor(this.container, { - enableSplitViewResizing: false, - renderSideBySide: this.renderSideBySide, - scrollBeyondLastLine: false, - readOnly: true, - minimap: { - enabled: false, - }, - hover: { - enabled: false, // disable default hover; - }, - occurrencesHighlight: false, - selectionHighlight: false, - renderLineHighlight: 'none', - contextmenu: false, - folding: true, - renderIndentGuides: false, - automaticLayout: false, - lineDecorationsWidth: 16, - overviewRulerBorder: false, - }); - this.resizeChecker = new ResizeChecker(this.container); - this.resizeChecker.on('resize', () => { - setTimeout(() => { - this.diffEditor!.layout(); - }); - }); - diffEditor.setModel({ - original: originalModel, - modified: modifiedModel, - }); - this.diffEditor = diffEditor; - const navi = monaco.editor.createDiffNavigator(diffEditor, { - followsCaret: true, - ignoreCharChanges: true, - }); - diffEditor.focus(); - navi.next(); - }); - } -} diff --git a/x-pack/legacy/plugins/code/public/monaco/monaco_helper.ts b/x-pack/legacy/plugins/code/public/monaco/monaco_helper.ts deleted file mode 100644 index 79e1141945708..0000000000000 --- a/x-pack/legacy/plugins/code/public/monaco/monaco_helper.ts +++ /dev/null @@ -1,164 +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 { editor } from 'monaco-editor'; - -import { ResizeChecker } from '../components/shared/resize_checker'; -import { EditorActions } from '../components/editor/editor'; - -import { toCanonicalUrl } from '../../common/uri_util'; -import { EditorService } from './editor_service'; -import { HoverController } from './hover/hover_controller'; -import { monaco } from './monaco'; -import { registerReferencesAction } from './references/references_action'; -import { registerEditor } from './single_selection_helper'; -import { TextModelResolverService } from './textmodel_resolver'; - -export class MonacoHelper { - public get initialized() { - return this.monaco !== null; - } - public decorations: string[] = []; - public editor: editor.IStandaloneCodeEditor | null = null; - private monaco: any | null = null; - private resizeChecker: ResizeChecker | null = null; - - constructor( - public readonly container: HTMLElement, - private readonly editorActions: EditorActions, - private urlQuery: string - ) { - this.handleCopy = this.handleCopy.bind(this); - } - public init() { - return new Promise(resolve => { - this.monaco = monaco; - const codeEditorService = new EditorService(this.getUrlQuery); - codeEditorService.setMonacoHelper(this); - this.editor = monaco.editor.create( - this.container!, - { - readOnly: true, - minimap: { - enabled: false, - }, - hover: { - enabled: false, // disable default hover; - }, - occurrencesHighlight: false, - selectionHighlight: false, - renderLineHighlight: 'none', - contextmenu: false, - folding: true, - scrollBeyondLastLine: false, - renderIndentGuides: false, - automaticLayout: false, - lineDecorationsWidth: 16, - }, - { - textModelService: new TextModelResolverService(monaco), - codeEditorService, - } - ); - registerEditor(this.editor); - this.resizeChecker = new ResizeChecker(this.container); - this.resizeChecker.on('resize', () => { - setTimeout(() => { - this.editor!.layout(); - }); - }); - registerReferencesAction(this.editor, this.getUrlQuery); - const hoverController: HoverController = new HoverController(this.editor); - hoverController.setReduxActions(this.editorActions); - document.addEventListener('copy', this.handleCopy); - document.addEventListener('cut', this.handleCopy); - resolve(this.editor); - }); - } - - updateUrlQuery = (q: string) => { - this.urlQuery = q; - }; - - getUrlQuery = () => { - return this.urlQuery; - }; - - public destroy = () => { - this.monaco = null; - document.removeEventListener('copy', this.handleCopy); - document.removeEventListener('cut', this.handleCopy); - - if (this.resizeChecker) { - this.resizeChecker!.destroy(); - } - }; - - public async loadFile( - repoUri: string, - file: string, - text: string, - lang: string, - revision: string = 'master' - ) { - if (!this.initialized) { - await this.init(); - } - const ed = this.editor!; - const oldModel = ed.getModel(); - if (oldModel) { - oldModel.dispose(); - } - ed.setModel(null); - const uri = this.monaco!.Uri.parse( - toCanonicalUrl({ schema: 'git:', repoUri, file, revision, pathType: 'blob' }) - ); - let newModel = this.monaco!.editor.getModel(uri); - if (!newModel) { - newModel = this.monaco!.editor.createModel(text, lang, uri); - } else { - newModel.setValue(text); - } - ed.setModel(newModel); - return ed; - } - - public revealPosition(line: number, pos: number) { - const position = { - lineNumber: line, - column: pos, - }; - this.decorations = this.editor!.deltaDecorations(this.decorations, [ - { - range: new this.monaco!.Range(line, 0, line, 0), - options: { - isWholeLine: true, - className: 'codeBlock__line--highlighted', - linesDecorationsClassName: 'codeBlock__line--highlighted', - }, - }, - ]); - this.editor!.setPosition(position); - this.editor!.revealLineInCenterIfOutsideViewport(line); - } - - public clearLineSelection() { - if (this.editor) { - this.decorations = this.editor.deltaDecorations(this.decorations, []); - } - } - - private handleCopy(e: any) { - if (this.editor && this.editor.hasTextFocus() && this.editor.hasWidgetFocus()) { - const selection = this.editor.getSelection(); - if (selection && !selection.isEmpty()) { - const text = this.editor.getModel()!.getValueInRange(selection); - e.clipboardData.setData('text/plain', text); - e.preventDefault(); - } - } - } -} diff --git a/x-pack/legacy/plugins/code/public/monaco/operation.ts b/x-pack/legacy/plugins/code/public/monaco/operation.ts deleted file mode 100644 index 1f0be75a5ea91..0000000000000 --- a/x-pack/legacy/plugins/code/public/monaco/operation.ts +++ /dev/null @@ -1,90 +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 { AsyncTask, Computer } from './computer'; - -enum OperationState { - IDLE, - DELAYED, - RUNNING, -} - -export class Operation { - public static DEFAULT_DELAY_TIME = 300; - private task: AsyncTask | null = null; - private state: OperationState = OperationState.IDLE; - private delay: number = Operation.DEFAULT_DELAY_TIME; - private timeout: any; - - constructor( - public readonly computer: Computer, - public readonly successCallback: (result: T) => void, - public readonly errorCallback: (error: Error) => void, - public readonly progressCallback: (progress: any) => void - ) {} - - public setDelay(delay: number) { - this.delay = delay; - } - - public start() { - if (this.state === OperationState.IDLE) { - this.task = this.computer.compute(); - this.triggerDelay(); - } - } - - public triggerDelay() { - this.cancelDelay(); - this.timeout = setTimeout(this.triggerAsyncTask.bind(this), this.delay); - this.state = OperationState.DELAYED; - } - - public cancel() { - if (this.state === OperationState.RUNNING) { - if (this.task) { - this.task.cancel(); - this.task = null; - } - } else if (this.state === OperationState.DELAYED) { - this.cancelDelay(); - } - this.state = OperationState.IDLE; - } - - private cancelDelay() { - if (this.timeout) { - clearTimeout(this.timeout); - this.timeout = null; - } - } - - private showLoading() { - this.progressCallback(this.computer.loadingMessage()); - } - - private triggerAsyncTask() { - if (this.task) { - this.state = OperationState.RUNNING; - const loadingDelay = setTimeout(this.showLoading.bind(this), this.delay); - - const task = this.task; - task.promise().then( - result => { - clearTimeout(loadingDelay); - if (task === this.task) { - this.successCallback(result); - } - }, - error => { - clearTimeout(loadingDelay); - if (task === this.task) { - this.errorCallback(error); - } - } - ); - } - } -} diff --git a/x-pack/legacy/plugins/code/public/monaco/override_monaco_styles.scss b/x-pack/legacy/plugins/code/public/monaco/override_monaco_styles.scss deleted file mode 100644 index 394f24bce37af..0000000000000 --- a/x-pack/legacy/plugins/code/public/monaco/override_monaco_styles.scss +++ /dev/null @@ -1,44 +0,0 @@ -.codeContainer__monaco { - .monaco-diff-editor .cursors-layer > .cursor, - .monaco-editor .cursors-layer > .cursor { - display: none !important; - } - - textarea.inputarea { - display: none !important; - } - - .monaco-diff-editor.mac .margin-view-overlays .line-numbers, - .monaco-editor.mac .margin-view-overlays .line-numbers { - cursor: pointer; - color: $euiColorMediumShade; - background-color: $euiColorLightestShade; - } - - .cldr.code-line-decoration + .cldr.folding { - left: -124px !important; - opacity: 1; - } - - span.mtk6 { - color: $euiColorSecondary; - } - - span.mtk29 { - color: $euiColorAccent; - } - - .diagonal-fill { - background: none; - } - - .monaco-diff-editor .line-insert, - .monaco-diff-editor .char-insert { - background-color: scale($euiColorVis0, -0.9); - } - - .monaco-diff-editor .line-delete, - .monaco-diff-editor .char-delete { - background-color: scale($euiColorDanger, -93%); - } -} diff --git a/x-pack/legacy/plugins/code/public/monaco/references/references_action.ts b/x-pack/legacy/plugins/code/public/monaco/references/references_action.ts deleted file mode 100644 index 8ee3a3abf09c7..0000000000000 --- a/x-pack/legacy/plugins/code/public/monaco/references/references_action.ts +++ /dev/null @@ -1,37 +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 { editor } from 'monaco-editor'; -import queryString from 'querystring'; -import url from 'url'; -import { parseSchema } from '../../../common/uri_util'; -import { history } from '../../utils/url'; - -export function registerReferencesAction( - e: editor.IStandaloneCodeEditor, - getUrlQuery: () => string -) { - e.addAction({ - id: 'editor.action.referenceSearch.trigger', - label: 'Find All References', - contextMenuGroupId: 'navigation', - contextMenuOrder: 1.5, - run(ed: editor.ICodeEditor) { - const position = ed.getPosition(); - const model = ed.getModel(); - if (model && position) { - const { uri } = parseSchema(model.uri.toString()); - const refUrl = `git:/${uri}!L${position.lineNumber - 1}:${position.column - 1}`; - const queries = url.parse(getUrlQuery(), true).query; - const query = queryString.stringify({ - ...queries, - tab: 'references', - refUrl, - }); - history.push(`${uri}?${query}`); - } - }, - }); -} diff --git a/x-pack/legacy/plugins/code/public/monaco/single_selection_helper.ts b/x-pack/legacy/plugins/code/public/monaco/single_selection_helper.ts deleted file mode 100644 index 73c177f4634e6..0000000000000 --- a/x-pack/legacy/plugins/code/public/monaco/single_selection_helper.ts +++ /dev/null @@ -1,33 +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 { editor } from 'monaco-editor'; - -const editors = new Set(); - -function clearSelection(ed: editor.IStandaloneCodeEditor) { - const sel = ed.getSelection(); - if (sel && !sel.isEmpty()) { - ed.setSelection({ - selectionStartLineNumber: sel.selectionStartLineNumber, - selectionStartColumn: sel.selectionStartColumn, - positionLineNumber: sel.selectionStartLineNumber, - positionColumn: sel.selectionStartColumn, - }); - } -} - -export function registerEditor(ed: editor.IStandaloneCodeEditor) { - editors.add(ed); - ed.onDidChangeCursorSelection(() => { - editors.forEach(e => { - if (e !== ed) { - clearSelection(e); - } - }); - }); - ed.onDidDispose(() => editors.delete(ed)); -} diff --git a/x-pack/legacy/plugins/code/public/monaco/textmodel_resolver.ts b/x-pack/legacy/plugins/code/public/monaco/textmodel_resolver.ts deleted file mode 100644 index 06fae9b59154a..0000000000000 --- a/x-pack/legacy/plugins/code/public/monaco/textmodel_resolver.ts +++ /dev/null @@ -1,72 +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 { editor, IDisposable, Uri } from 'monaco-editor'; -import { npStart } from 'ui/new_platform'; - -import { ImmortalReference } from './immortal_reference'; - -export interface IReference extends IDisposable { - readonly object: T; -} - -export interface ITextModelService { - /** - * Provided a resource URI, it will return a model reference - * which should be disposed once not needed anymore. - */ - createModelReference(resource: Uri): Promise>; - - /** - * Registers a specific `scheme` content provider. - */ - registerTextModelContentProvider(scheme: string, provider: any): IDisposable; -} - -export class TextModelResolverService implements ITextModelService { - constructor(private readonly monaco: any) {} - - public async createModelReference(resource: Uri): Promise> { - let model = this.monaco.editor.getModel(resource); - if (!model) { - const result = await this.fetchText(resource); - if (!result) { - return new ImmortalReference(null); - } else { - model = this.monaco.editor.createModel(result.text, result.lang, resource); - } - } - return new ImmortalReference({ textEditorModel: model }); - } - - public registerTextModelContentProvider(scheme: string, provider: any): IDisposable { - return { - dispose() { - /* no op */ - }, - }; - } - - private async fetchText(resource: Uri) { - const repo = `${resource.authority}${resource.path}`; - const revision = resource.query; - const file = resource.fragment; - const response = await fetch( - npStart.core.http.basePath.prepend(`/api/code/repo/${repo}/blob/${revision}/${file}`) - ); - if (response.status === 200) { - const contentType = response.headers.get('Content-Type'); - - if (contentType && contentType.startsWith('text/')) { - const lang = response.headers.get('lang'); - const text = await response.text(); - return { text, lang }; - } - } else { - return null; - } - } -} diff --git a/x-pack/legacy/plugins/code/public/plugin.ts b/x-pack/legacy/plugins/code/public/plugin.ts deleted file mode 100644 index 48b295ef67d9e..0000000000000 --- a/x-pack/legacy/plugins/code/public/plugin.ts +++ /dev/null @@ -1,25 +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 { PluginInitializerContext, CoreSetup, CoreStart } from 'src/core/public'; -import { startApp } from './app'; - -export class Plugin { - constructor(initializerContext: PluginInitializerContext) {} - - public setup(core: CoreSetup) { - // called when plugin is setting up - } - - public start(core: CoreStart) { - // called after all plugins are set up - startApp(core); - } - - public stop() { - // called when plugin is torn down, aka window.onbeforeunload - } -} diff --git a/x-pack/legacy/plugins/code/public/reducers/blame.ts b/x-pack/legacy/plugins/code/public/reducers/blame.ts deleted file mode 100644 index 65c9c1fce86d6..0000000000000 --- a/x-pack/legacy/plugins/code/public/reducers/blame.ts +++ /dev/null @@ -1,56 +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 produce from 'immer'; -import { Action, handleActions } from 'redux-actions'; - -import { GitBlame } from '../../common/git_blame'; -import { loadBlame, loadBlameFailed, loadBlameSuccess } from '../actions/blame'; -import { routePathChange, repoChange, revisionChange, filePathChange } from '../actions/route'; - -export interface BlameState { - blames: GitBlame[]; - loading: boolean; - error?: Error; -} - -const initialState: BlameState = { - blames: [], - loading: false, -}; - -const clearState = (state: BlameState) => - produce(state, draft => { - draft.blames = initialState.blames; - draft.loading = initialState.loading; - draft.error = undefined; - }); - -type BlameStatePayload = GitBlame[] & Error; - -export const blame = handleActions( - { - [String(loadBlame)]: state => - produce(state, draft => { - draft.loading = true; - }), - [String(loadBlameSuccess)]: (state, action: Action) => - produce(state, draft => { - draft.blames = action.payload!; - draft.loading = false; - }), - [String(loadBlameFailed)]: (state, action: Action) => - produce(state, draft => { - draft.loading = false; - draft.error = action.payload; - draft.blames = []; - }), - [String(routePathChange)]: clearState, - [String(repoChange)]: clearState, - [String(revisionChange)]: clearState, - [String(filePathChange)]: clearState, - }, - initialState -); diff --git a/x-pack/legacy/plugins/code/public/reducers/commit.ts b/x-pack/legacy/plugins/code/public/reducers/commit.ts deleted file mode 100644 index 1c613f31e4d72..0000000000000 --- a/x-pack/legacy/plugins/code/public/reducers/commit.ts +++ /dev/null @@ -1,42 +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 produce from 'immer'; -import { Action, handleActions } from 'redux-actions'; - -import { CommitDiff } from '../../common/git_diff'; -import { loadCommit, loadCommitFailed, loadCommitSuccess } from '../actions/commit'; - -export interface CommitState { - commit: CommitDiff | null; - loading: boolean; -} - -const initialState: CommitState = { - commit: null, - loading: false, -}; - -type CommitPayload = CommitDiff & Error; - -export const commit = handleActions( - { - [String(loadCommit)]: state => - produce(state, draft => { - draft.loading = true; - }), - [String(loadCommitSuccess)]: (state, action: Action) => - produce(state, draft => { - draft.commit = action.payload!; - draft.loading = false; - }), - [String(loadCommitFailed)]: state => - produce(state, draft => { - draft.commit = null; - draft.loading = false; - }), - }, - initialState -); diff --git a/x-pack/legacy/plugins/code/public/reducers/editor.ts b/x-pack/legacy/plugins/code/public/reducers/editor.ts deleted file mode 100644 index ed7dc780b4407..0000000000000 --- a/x-pack/legacy/plugins/code/public/reducers/editor.ts +++ /dev/null @@ -1,107 +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 produce from 'immer'; -import { handleActions, Action } from 'redux-actions'; -import { Hover, TextDocumentPositionParams } from 'vscode-languageserver'; -import { - closePanel, - findReferences, - findReferencesFailed, - findReferencesSuccess, - GroupedRepoResults, - hoverResult, - revealPosition, - PanelResults, - findDefinitions, - findDefinitionsSuccess, - findDefinitionsFailed, -} from '../actions'; - -export interface EditorState { - loading: boolean; - panelShowing: 'references' | 'definitions' | undefined; - panelContents: GroupedRepoResults[]; - hover?: Hover; - currentHover?: Hover; - refPayload?: TextDocumentPositionParams; - revealPosition?: Position; - panelTitle: string; -} -const initialState: EditorState = { - loading: false, - panelShowing: undefined, - panelContents: [], - panelTitle: '', -}; - -type EditorPayload = PanelResults & Hover & TextDocumentPositionParams & Position & string; - -function panelInit(draft: EditorState, action: Action) { - draft.refPayload = action.payload!; - draft.loading = true; - draft.panelContents = initialState.panelContents; - draft.panelTitle = initialState.panelTitle; -} - -function panelSuccess(draft: EditorState, action: Action) { - const { title, repos } = action.payload!; - draft.panelContents = repos; - draft.panelTitle = title; - draft.loading = false; -} -function panelFailed(draft: EditorState) { - draft.panelContents = []; - draft.loading = false; - delete draft.refPayload; -} - -export const editor = handleActions( - { - [String(findReferences)]: (state, action: Action) => - produce(state, (draft: EditorState) => { - panelInit(draft, action); - draft.panelShowing = 'references'; - }), - [String(findReferencesSuccess)]: (state, action: Action) => - produce(state, (draft: EditorState) => { - panelSuccess(draft, action); - }), - [String(findReferencesFailed)]: state => - produce(state, (draft: EditorState) => { - panelFailed(draft); - }), - [String(findDefinitions)]: (state, action: Action) => - produce(state, (draft: EditorState) => { - panelInit(draft, action); - draft.panelShowing = 'definitions'; - }), - [String(findDefinitionsSuccess)]: (state, action: Action) => - produce(state, (draft: EditorState) => { - panelSuccess(draft, action); - }), - [String(findDefinitionsFailed)]: state => - produce(state, (draft: EditorState) => { - panelFailed(draft); - }), - [String(closePanel)]: state => - produce(state, (draft: EditorState) => { - draft.panelShowing = undefined; - draft.loading = false; - delete draft.refPayload; - draft.panelContents = []; - draft.panelTitle = ''; - }), - [String(hoverResult)]: (state, action: Action) => - produce(state, (draft: EditorState) => { - draft.currentHover = action.payload!; - }), - [String(revealPosition)]: (state, action: Action) => - produce(state, (draft: EditorState) => { - draft.revealPosition = action.payload!; - }), - }, - initialState -); diff --git a/x-pack/legacy/plugins/code/public/reducers/file.ts b/x-pack/legacy/plugins/code/public/reducers/file.ts deleted file mode 100644 index 59bdaee288122..0000000000000 --- a/x-pack/legacy/plugins/code/public/reducers/file.ts +++ /dev/null @@ -1,72 +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 produce from 'immer'; -import { handleActions, Action } from 'redux-actions'; -import { - fetchFileFailed, - FetchFileResponse, - fetchFileSuccess, - routeChange, - setNotFound, - fetchFile, - FetchFilePayload, -} from '../actions'; -import { routePathChange, repoChange, revisionChange, filePathChange } from '../actions/route'; - -export interface FileState { - file?: FetchFileResponse; - isNotFound: boolean; - loading: boolean; -} - -const initialState: FileState = { - isNotFound: false, - loading: false, -}; - -const clearState = (state: FileState) => - produce(state, draft => { - draft.file = undefined; - draft.isNotFound = initialState.isNotFound; - draft.loading = initialState.loading; - }); - -type FilePayload = FetchFileResponse & boolean & FetchFilePayload; - -export const file = handleActions( - { - [String(fetchFile)]: (state, action: Action) => - produce(state, draft => { - draft.loading = true; - }), - [String(fetchFileSuccess)]: (state, action: Action) => - produce(state, draft => { - draft.file = action.payload; - draft.isNotFound = false; - draft.loading = false; - }), - [String(fetchFileFailed)]: state => - produce(state, draft => { - draft.file = undefined; - draft.loading = false; - }), - [String(setNotFound)]: (state, action: Action) => - produce(state, draft => { - draft.isNotFound = action.payload!; - draft.loading = false; - }), - [String(routeChange)]: state => - produce(state, draft => { - draft.isNotFound = false; - }), - [String(routePathChange)]: clearState, - [String(repoChange)]: clearState, - [String(revisionChange)]: clearState, - [String(filePathChange)]: clearState, - }, - initialState -); diff --git a/x-pack/legacy/plugins/code/public/reducers/file_tree.ts b/x-pack/legacy/plugins/code/public/reducers/file_tree.ts deleted file mode 100644 index 6c9d69d646703..0000000000000 --- a/x-pack/legacy/plugins/code/public/reducers/file_tree.ts +++ /dev/null @@ -1,154 +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 produce from 'immer'; -import { Action, handleActions } from 'redux-actions'; -import { FileTree, FileTreeItemType, sortFileTree } from '../../model'; -import { - fetchRepoTree, - fetchRepoTreeFailed, - fetchRepoTreeSuccess, - RepoTreePayload, - fetchRootRepoTreeSuccess, - fetchRootRepoTreeFailed, - dirNotFound, - FetchRepoTreePayload, -} from '../actions'; -import { routePathChange, repoChange, revisionChange } from '../actions/route'; - -function mergeNode(a: FileTree, b: FileTree): FileTree { - const childrenMap: { [name: string]: FileTree } = {}; - if (a.children) { - a.children.forEach(child => { - childrenMap[child.name] = child; - }); - } - if (b.children) { - b.children.forEach(childB => { - const childA = childrenMap[childB.name]; - if (childA) { - childrenMap[childB.name] = mergeNode(childA, childB); - } else { - childrenMap[childB.name] = childB; - } - }); - } - return { - ...a, - ...b, - children: Object.values(childrenMap).sort(sortFileTree), - }; -} - -export function getPathOfTree(tree: FileTree, paths: string[]) { - let child: FileTree | undefined = tree; - for (const p of paths) { - if (child && child.children) { - child = child.children.find(c => c.name === p); - } else { - return null; - } - } - return child; -} - -export interface FileTreeState { - tree: FileTree; - fileTreeLoadingPaths: string[]; - // store not found directory as an array to calculate `notFound` flag by finding whether path is in this array - notFoundDirs: string[]; - revision: string; -} - -const initialState: FileTreeState = { - tree: { - name: '', - path: '', - type: FileTreeItemType.Directory, - }, - fileTreeLoadingPaths: [''], - notFoundDirs: [], - revision: '', -}; - -const clearState = (state: FileTreeState) => - produce(state, draft => { - draft.tree = initialState.tree; - draft.fileTreeLoadingPaths = initialState.fileTreeLoadingPaths; - draft.notFoundDirs = initialState.notFoundDirs; - draft.revision = initialState.revision; - }); - -type FileTreePayload = FetchRepoTreePayload & RepoTreePayload & FileTree & string; - -export const fileTree = handleActions( - { - [String(fetchRepoTree)]: (state, action: Action) => - produce(state, draft => { - draft.fileTreeLoadingPaths.push(action.payload!.path); - }), - [String(fetchRepoTreeSuccess)]: (state, action: Action) => - produce(state, draft => { - draft.revision = action.payload!.revision; - draft.notFoundDirs = draft.notFoundDirs.filter(dir => dir !== action.payload!.path); - draft.fileTreeLoadingPaths = draft.fileTreeLoadingPaths.filter( - p => p !== action.payload!.path && p !== '' - ); - const { tree, path, withParents } = action.payload!; - let sortedTree: FileTree | undefined = tree; - while (sortedTree && sortedTree.children && sortedTree.children.length > 0) { - sortedTree.children = sortedTree.children.sort(sortFileTree); - // each time the tree should only have one child that has children - sortedTree = sortedTree.children.find( - child => child.children && child.children.length > 0 - ); - } - if (withParents || path === '/' || path === '') { - draft.tree = mergeNode(draft.tree, tree); - } else { - const parentsPath = path.split('/'); - const lastPath = parentsPath.pop(); - const parent = getPathOfTree(draft.tree, parentsPath); - if (parent) { - parent.children = parent.children || []; - const idx = parent.children.findIndex(c => c.name === lastPath); - if (idx >= 0) { - parent.children[idx] = tree; - } else { - parent.children.push(tree); - } - } - } - }), - [String(fetchRootRepoTreeSuccess)]: ( - state, - action: Action<{ revision: string; tree: FileTree }> - ) => - produce(state, draft => { - draft.fileTreeLoadingPaths = draft.fileTreeLoadingPaths.filter(p => p !== '/' && p !== ''); - draft.tree = mergeNode(draft.tree, action.payload!.tree); - draft.revision = action.payload!.revision; - }), - [String(fetchRootRepoTreeFailed)]: state => - produce(state, draft => { - draft.fileTreeLoadingPaths = draft.fileTreeLoadingPaths.filter(p => p !== '/' && p !== ''); - }), - [String(dirNotFound)]: (state, action: Action) => - produce(state, draft => { - draft.notFoundDirs.push(action.payload!); - }), - [String(fetchRepoTreeFailed)]: (state, action: Action) => - produce(state, draft => { - draft.fileTreeLoadingPaths = draft.fileTreeLoadingPaths.filter( - p => p !== action.payload!.path && p !== '' - ); - }), - [String(routePathChange)]: clearState, - [String(repoChange)]: clearState, - [String(revisionChange)]: clearState, - }, - initialState -); diff --git a/x-pack/legacy/plugins/code/public/reducers/index.ts b/x-pack/legacy/plugins/code/public/reducers/index.ts deleted file mode 100644 index 4ae539b1b3f97..0000000000000 --- a/x-pack/legacy/plugins/code/public/reducers/index.ts +++ /dev/null @@ -1,61 +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 { combineReducers, ReducersMapObject } from 'redux'; - -import { blame, BlameState } from './blame'; -import { commit, CommitState } from './commit'; -import { editor, EditorState } from './editor'; -import { file, FileState } from './file'; -import { languageServer, LanguageServerState } from './language_server'; -import { repositoryManagement, RepositoryManagementState } from './repository_management'; -import { route, RouteState } from './route'; -import { search, SearchState } from './search'; -import { setup, SetupState } from './setup'; -import { shortcuts, ShortcutsState } from './shortcuts'; -import { status, StatusState } from './status'; -import { symbol, SymbolState } from './symbol'; -import { RevisionState, revision } from './revision'; -import { RepositoryState, repository } from './repository'; -import { fileTree, FileTreeState } from './file_tree'; - -export interface RootState { - blame: BlameState; - commit: CommitState; - editor: EditorState; - file: FileState; - fileTree: FileTreeState; - languageServer: LanguageServerState; - repository: RepositoryState; - repositoryManagement: RepositoryManagementState; - revision: RevisionState; - route: RouteState; - search: SearchState; - setup: SetupState; - shortcuts: ShortcutsState; - status: StatusState; - symbol: SymbolState; -} - -const reducers = { - blame, - commit, - editor, - file, - fileTree, - languageServer, - repository, - repositoryManagement, - revision, - route, - search, - setup, - shortcuts, - status, - symbol, -}; - -export const rootReducer = combineReducers(reducers as ReducersMapObject); diff --git a/x-pack/legacy/plugins/code/public/reducers/language_server.ts b/x-pack/legacy/plugins/code/public/reducers/language_server.ts deleted file mode 100644 index d4755e249f3b4..0000000000000 --- a/x-pack/legacy/plugins/code/public/reducers/language_server.ts +++ /dev/null @@ -1,66 +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 produce from 'immer'; -import { Action, handleActions } from 'redux-actions'; - -import { LanguageServer, LanguageServerStatus } from '../../common/language_server'; -import { - loadLanguageServers, - loadLanguageServersFailed, - loadLanguageServersSuccess, - requestInstallLanguageServer, - requestInstallLanguageServerSuccess, -} from '../actions/language_server'; - -export interface LanguageServerState { - languageServers: LanguageServer[]; - loading: boolean; - installServerLoading: { [ls: string]: boolean }; -} - -const initialState: LanguageServerState = { - languageServers: [], - loading: false, - installServerLoading: {}, -}; - -type LanguageServerPayload = string & LanguageServer[]; - -export const languageServer = handleActions( - { - [String(loadLanguageServers)]: state => - produce(state, draft => { - draft.loading = true; - }), - [String(loadLanguageServersSuccess)]: ( - state: LanguageServerState, - action: Action - ) => - produce(state, draft => { - draft.languageServers = action.payload!; - draft.loading = false; - }), - [String(loadLanguageServersFailed)]: state => - produce(state, draft => { - draft.languageServers = []; - draft.loading = false; - }), - [String(requestInstallLanguageServer)]: (state, action: Action) => - produce(state, draft => { - draft.installServerLoading[action.payload!] = true; - }), - [String(requestInstallLanguageServerSuccess)]: ( - state: LanguageServerState, - action: Action - ) => - produce(state, draft => { - draft.installServerLoading[action.payload!] = false; - draft.languageServers.find(ls => ls.name === action.payload)!.status = - LanguageServerStatus.READY; - }), - }, - initialState -); diff --git a/x-pack/legacy/plugins/code/public/reducers/repository.ts b/x-pack/legacy/plugins/code/public/reducers/repository.ts deleted file mode 100644 index 2ef4e4c09fbab..0000000000000 --- a/x-pack/legacy/plugins/code/public/reducers/repository.ts +++ /dev/null @@ -1,50 +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 produce from 'immer'; -import { Action, handleActions } from 'redux-actions'; - -import { Repository } from '../../model'; - -import { loadRepo, loadRepoSuccess, loadRepoFailed } from '../actions'; -import { routePathChange, repoChange } from '../actions/route'; - -export interface RepositoryState { - repository?: Repository; - repoNotFound: boolean; -} - -const initialState: RepositoryState = { - repoNotFound: false, -}; - -const clearState = (state: RepositoryState) => - produce(state, draft => { - draft.repository = undefined; - draft.repoNotFound = initialState.repoNotFound; - }); - -export const repository = handleActions( - { - [String(loadRepo)]: state => - produce(state, draft => { - draft.repository = undefined; - draft.repoNotFound = false; - }), - [String(loadRepoSuccess)]: (state, action: Action) => - produce(state, draft => { - draft.repository = action.payload; - draft.repoNotFound = false; - }), - [String(loadRepoFailed)]: state => - produce(state, draft => { - draft.repository = undefined; - draft.repoNotFound = true; - }), - [String(routePathChange)]: clearState, - [String(repoChange)]: clearState, - }, - initialState -); diff --git a/x-pack/legacy/plugins/code/public/reducers/repository_management.ts b/x-pack/legacy/plugins/code/public/reducers/repository_management.ts deleted file mode 100644 index 217aead14f48f..0000000000000 --- a/x-pack/legacy/plugins/code/public/reducers/repository_management.ts +++ /dev/null @@ -1,143 +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 produce from 'immer'; -import { Action, handleActions } from 'redux-actions'; - -import { i18n } from '@kbn/i18n'; -import { Repository, RepoConfigs, RepositoryConfig } from '../../model'; - -import { - closeToast, - deleteRepoFinished, - fetchRepos, - fetchReposFailed, - fetchReposSuccess, - importRepo, - importRepoFailed, - importRepoSuccess, - fetchRepoConfigSuccess, - loadConfigsSuccess, - RepoLangserverConfigs, -} from '../actions'; - -export enum ToastType { - danger = 'danger', - success = 'success', - warning = 'warning', -} - -export interface RepositoryManagementState { - repoConfigs?: RepoConfigs; - repoLangseverConfigs: { [key: string]: RepositoryConfig }; - repositories: Repository[]; - error?: Error; - loading: boolean; - importLoading: boolean; - showToast: boolean; - toastMessage?: string; - toastType?: ToastType; -} - -const initialState: RepositoryManagementState = { - repositories: [], - repoLangseverConfigs: {}, - loading: false, - importLoading: false, - showToast: false, -}; - -type RepositoryManagementStatePayload = RepoConfigs & - RepoLangserverConfigs & - Repository & - RepositoryConfig & - Repository[] & - Error & - string; - -export const repositoryManagement = handleActions< - RepositoryManagementState, - RepositoryManagementStatePayload ->( - { - [String(fetchRepos)]: state => - produce(state, draft => { - draft.loading = true; - }), - [String(fetchReposSuccess)]: (state, action: Action) => - produce(state, draft => { - draft.loading = false; - draft.repositories = action.payload || []; - }), - [String(fetchReposFailed)]: (state, action: Action) => { - if (action.payload) { - return produce(state, draft => { - draft.error = action.payload; - draft.loading = false; - }); - } else { - return state; - } - }, - [String(deleteRepoFinished)]: (state, action: Action) => - produce(state, draft => { - draft.repositories = state.repositories.filter(repo => repo.uri !== action.payload); - }), - [String(importRepo)]: state => - produce(state, draft => { - draft.importLoading = true; - }), - [String(importRepoSuccess)]: (state, action: Action) => - // TODO is it possible and how to deal with action.payload === undefined? - produce(state, draft => { - draft.importLoading = false; - draft.showToast = true; - draft.toastType = ToastType.success; - draft.toastMessage = i18n.translate( - 'xpack.code.repositoryManagement.repoSubmittedMessage', - { - defaultMessage: '{name} has been successfully submitted!', - values: { name: action.payload!.name }, - } - ); - draft.repositories = [...state.repositories, action.payload!]; - }), - [String(importRepoFailed)]: (state, action: Action) => - produce(state, draft => { - if (action.payload) { - if (action.payload.response && action.payload.response.status === 304) { - draft.toastMessage = i18n.translate( - 'xpack.code.repositoryManagement.repoImportedMessage', - { - defaultMessage: 'This Repository has already been imported!', - } - ); - draft.showToast = true; - draft.toastType = ToastType.warning; - draft.importLoading = false; - } else { - // TODO add localication for those messages - draft.toastMessage = action.payload.body.message; - draft.showToast = true; - draft.toastType = ToastType.danger; - draft.importLoading = false; - } - } - }), - [String(closeToast)]: state => - produce(state, draft => { - draft.showToast = false; - }), - [String(fetchRepoConfigSuccess)]: (state, action: Action) => - produce(state, draft => { - draft.repoConfigs = action.payload; - }), - [String(loadConfigsSuccess)]: (state, action: Action) => - produce(state, draft => { - draft.repoLangseverConfigs = action.payload!; - }), - }, - initialState -); diff --git a/x-pack/legacy/plugins/code/public/reducers/revision.ts b/x-pack/legacy/plugins/code/public/reducers/revision.ts deleted file mode 100644 index 3c4839ec9aa4b..0000000000000 --- a/x-pack/legacy/plugins/code/public/reducers/revision.ts +++ /dev/null @@ -1,89 +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 produce from 'immer'; -import { handleActions, Action } from 'redux-actions'; -import { CommitInfo, ReferenceInfo, ReferenceType } from '../../model/commit'; -import { - fetchMoreCommits, - fetchRepoBranchesSuccess, - fetchRepoCommitsSuccess, - fetchTreeCommits, - fetchTreeCommitsFailed, - fetchTreeCommitsSuccess, - TreeCommitPayload, -} from '../actions'; -import { routePathChange, repoChange } from '../actions/route'; - -export interface RevisionState { - branches: ReferenceInfo[]; - tags: ReferenceInfo[]; - treeCommits: { [path: string]: CommitInfo[] }; - loadingCommits: boolean; - commitsFullyLoaded: { [path: string]: boolean }; -} - -const initialState: RevisionState = { - branches: [], - tags: [], - treeCommits: {}, - loadingCommits: false, - commitsFullyLoaded: {}, -}; - -const clearState = (state: RevisionState) => - produce(state, draft => { - draft.branches = initialState.branches; - draft.tags = initialState.tags; - draft.treeCommits = initialState.treeCommits; - draft.loadingCommits = initialState.loadingCommits; - draft.commitsFullyLoaded = initialState.commitsFullyLoaded; - }); - -type RevisionPayload = ReferenceInfo[] & CommitInfo[] & TreeCommitPayload; - -export const revision = handleActions( - { - [String(fetchRepoCommitsSuccess)]: (state, action: Action) => - produce(state, draft => { - draft.treeCommits[''] = action.payload!; - draft.loadingCommits = false; - }), - [String(fetchMoreCommits)]: state => - produce(state, draft => { - draft.loadingCommits = true; - }), - [String(fetchRepoBranchesSuccess)]: (state, action: Action) => - produce(state, draft => { - const references = action.payload!; - draft.tags = references.filter(r => r.type === ReferenceType.TAG); - draft.branches = references.filter(r => r.type !== ReferenceType.TAG); - }), - [String(fetchTreeCommits)]: state => - produce(state, draft => { - draft.loadingCommits = true; - }), - [String(fetchTreeCommitsFailed)]: state => - produce(state, draft => { - draft.loadingCommits = false; - }), - [String(fetchTreeCommitsSuccess)]: (state, action: Action) => - produce(state, draft => { - const { path, commits, append } = action.payload!; - if (commits.length === 0) { - draft.commitsFullyLoaded[path] = true; - } else if (append) { - draft.treeCommits[path] = draft.treeCommits[path].concat(commits); - } else { - draft.treeCommits[path] = commits; - } - draft.loadingCommits = false; - }), - [String(routePathChange)]: clearState, - [String(repoChange)]: clearState, - }, - initialState -); diff --git a/x-pack/legacy/plugins/code/public/reducers/route.ts b/x-pack/legacy/plugins/code/public/reducers/route.ts deleted file mode 100644 index c6d9e50dc6d83..0000000000000 --- a/x-pack/legacy/plugins/code/public/reducers/route.ts +++ /dev/null @@ -1,29 +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 produce from 'immer'; -import { Action, handleActions } from 'redux-actions'; - -import { routeChange } from '../actions'; - -export interface RouteState { - match: any; - previousMatch?: any; -} - -const initialState: RouteState = { - match: {}, -}; - -export const route = handleActions( - { - [String(routeChange)]: (state: RouteState, action: Action) => - produce(state, draft => { - draft.previousMatch = state.match; - draft.match = action.payload; - }), - }, - initialState -); diff --git a/x-pack/legacy/plugins/code/public/reducers/search.ts b/x-pack/legacy/plugins/code/public/reducers/search.ts deleted file mode 100644 index 391f6ac76640e..0000000000000 --- a/x-pack/legacy/plugins/code/public/reducers/search.ts +++ /dev/null @@ -1,243 +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 produce from 'immer'; -import querystring from 'querystring'; -import { Action, handleActions } from 'redux-actions'; -import { history } from '../utils/url'; - -import { - DocumentSearchResult, - RepositoryUri, - SearchOptions, - SearchScope, - RepositorySearchResult, - Repository, -} from '../../model'; -import { - changeSearchScope, - documentSearch as documentSearchQuery, - documentSearchFailed, - DocumentSearchPayload, - documentSearchSuccess, - repositorySearch as repositorySearchAction, - repositorySearchFailed, - RepositorySearchPayload, - repositorySearchSuccess, - saveSearchOptions, - searchReposForScope, - searchReposForScopeSuccess, - suggestionSearch, - turnOffDefaultRepoScope, - turnOnDefaultRepoScope, -} from '../actions'; -import { RepositoryUtils } from '../../common/repository_utils'; - -export interface SearchState { - scope: SearchScope; - query: string; - page?: number; - languages?: Set; - repositories?: Set; - isLoading: boolean; - isScopeSearchLoading: boolean; - error?: Error; - documentSearchResults?: DocumentSearchResult; - repositorySearchResults?: any; - searchOptions: SearchOptions; - scopeSearchResults: RepositorySearchResult; -} - -const repositories: Repository[] = []; - -const getRepoScopeFromUrl = () => { - const { repoScope } = querystring.parse(history.location.search.replace('?', '')); - if (repoScope) { - return String(repoScope) - .split(',') - .map(r => ({ - uri: r, - org: RepositoryUtils.orgNameFromUri(r), - name: RepositoryUtils.repoNameFromUri(r), - })) as Repository[]; - } else { - return []; - } -}; - -const initialState: SearchState = { - query: '', - isLoading: false, - isScopeSearchLoading: false, - scope: SearchScope.DEFAULT, - searchOptions: { - repoScope: getRepoScopeFromUrl(), - defaultRepoScopeOn: false, - }, - scopeSearchResults: { repositories, total: 0, took: 0 }, -}; - -type SearchPayload = DocumentSearchPayload & - DocumentSearchResult & - Error & - string & - SearchScope & - SearchOptions & - Repository & - RepositorySearchResult; - -export const search = handleActions( - { - [String(changeSearchScope)]: (state: SearchState, action: Action) => - produce(state, draft => { - if (action.payload && Object.values(SearchScope).includes(action.payload)) { - draft.scope = action.payload!; - } else { - draft.scope = SearchScope.DEFAULT; - } - draft.isLoading = false; - }), - [String(documentSearchQuery)]: (state: SearchState, action: Action) => - produce(state, draft => { - if (action.payload) { - draft.query = action.payload.query; - draft.page = parseInt(action.payload.page as string, 10); - if (action.payload.languages) { - draft.languages = new Set(decodeURIComponent(action.payload.languages).split(',')); - } else { - draft.languages = new Set(); - } - if (action.payload.repositories) { - draft.repositories = new Set( - decodeURIComponent(action.payload.repositories).split(',') - ); - } else { - draft.repositories = new Set(); - } - draft.isLoading = true; - delete draft.error; - } - }), - [String(documentSearchSuccess)]: (state: SearchState, action: Action) => - produce(state, (draft: SearchState) => { - const { - from, - page, - totalPage, - results, - total, - repoAggregations, - langAggregations, - took, - } = action.payload!; - draft.isLoading = false; - - const repoStats = repoAggregations!.map(agg => { - return { - name: agg.key, - value: agg.doc_count, - }; - }); - - const languageStats = langAggregations!.map(agg => { - return { - name: agg.key, - value: agg.doc_count, - }; - }); - - draft.documentSearchResults = { - ...draft.documentSearchResults, - query: state.query, - total, - took, - stats: { - total, - from: from as number, - to: from! + results!.length, - page: page!, - totalPage: totalPage!, - repoStats, - languageStats, - }, - results, - }; - }), - [String(documentSearchFailed)]: (state: SearchState, action: Action) => { - if (action.payload) { - return produce(state, draft => { - draft.isLoading = false; - draft.error = action.payload!; - }); - } else { - return state; - } - }, - [String(suggestionSearch)]: (state: SearchState, action: Action) => - produce(state, draft => { - if (action.payload) { - draft.query = action.payload; - } - }), - [String(repositorySearchAction)]: ( - state: SearchState, - action: Action - ) => - produce(state, draft => { - if (action.payload) { - draft.query = action.payload.query; - draft.isLoading = true; - delete draft.error; - delete draft.repositorySearchResults; - } - }), - [String(repositorySearchSuccess)]: ( - state: SearchState, - action: Action - ) => - produce(state, draft => { - draft.repositorySearchResults = action.payload; - draft.isLoading = false; - }), - [String(repositorySearchFailed)]: (state: SearchState, action: Action) => { - if (action.payload) { - return produce(state, draft => { - draft.isLoading = false; - draft.error = action.payload; - }); - } else { - return state; - } - }, - [String(saveSearchOptions)]: (state: SearchState, action: Action) => - produce(state, draft => { - draft.searchOptions = action.payload!; - }), - [String(searchReposForScope)]: (state: SearchState, action: Action) => - produce(state, draft => { - draft.isScopeSearchLoading = true; - }), - [String(searchReposForScopeSuccess)]: ( - state: SearchState, - action: Action - ) => - produce(state, draft => { - draft.scopeSearchResults = action.payload!; - draft.isScopeSearchLoading = false; - }), - [String(turnOnDefaultRepoScope)]: (state: SearchState, action: Action) => - produce(state, draft => { - draft.searchOptions.defaultRepoScope = action.payload; - draft.searchOptions.defaultRepoScopeOn = true; - }), - [String(turnOffDefaultRepoScope)]: (state: SearchState) => - produce(state, draft => { - delete draft.searchOptions.defaultRepoScope; - draft.searchOptions.defaultRepoScopeOn = false; - }), - }, - initialState -); diff --git a/x-pack/legacy/plugins/code/public/reducers/setup.ts b/x-pack/legacy/plugins/code/public/reducers/setup.ts deleted file mode 100644 index c7850f527828f..0000000000000 --- a/x-pack/legacy/plugins/code/public/reducers/setup.ts +++ /dev/null @@ -1,30 +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 produce from 'immer'; -import { handleActions } from 'redux-actions'; - -import { checkSetupFailed, checkSetupSuccess } from '../actions'; - -export interface SetupState { - ok?: boolean; -} - -const initialState: SetupState = {}; - -export const setup = handleActions( - { - [String(checkSetupFailed)]: state => - produce(state, draft => { - draft.ok = false; - }), - [String(checkSetupSuccess)]: state => - produce(state, draft => { - draft.ok = true; - }), - }, - initialState -); diff --git a/x-pack/legacy/plugins/code/public/reducers/shortcuts.ts b/x-pack/legacy/plugins/code/public/reducers/shortcuts.ts deleted file mode 100644 index 655a3fecee72a..0000000000000 --- a/x-pack/legacy/plugins/code/public/reducers/shortcuts.ts +++ /dev/null @@ -1,59 +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 produce from 'immer'; -import { Action, handleActions } from 'redux-actions'; - -import { registerShortcut, toggleHelp, unregisterShortcut } from '../actions'; -import { HotKey } from '../components/shortcuts'; - -export interface ShortcutsState { - showHelp: boolean; - shortcuts: HotKey[]; -} - -const helpShortcut: HotKey = { - key: '?', - help: 'Display this page', - modifier: new Map(), - onPress: dispatch => { - dispatch(toggleHelp(null)); - }, -}; - -const initialState: ShortcutsState = { - showHelp: false, - shortcuts: [helpShortcut], -}; - -type ShortcutPayload = boolean & null & HotKey; - -export const shortcuts = handleActions( - { - [String(toggleHelp)]: (state, action: Action) => - produce(state, draft => { - if (action.payload === null) { - draft.showHelp = !state.showHelp; - } else { - draft.showHelp = action.payload!; - } - }), - [String(registerShortcut)]: (state, action: Action) => - produce(state, draft => { - const hotKey = action.payload as HotKey; - draft.shortcuts.push(hotKey); - }), - [String(unregisterShortcut)]: (state, action: Action) => - produce(state, draft => { - const hotKey = action.payload as HotKey; - const idx = state.shortcuts.indexOf(hotKey); - if (idx >= 0) { - draft.shortcuts.splice(idx, 1); - } - }), - }, - initialState -); diff --git a/x-pack/legacy/plugins/code/public/reducers/status.ts b/x-pack/legacy/plugins/code/public/reducers/status.ts deleted file mode 100644 index 4e57d5e7e7565..0000000000000 --- a/x-pack/legacy/plugins/code/public/reducers/status.ts +++ /dev/null @@ -1,236 +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 produce from 'immer'; -import { Action, handleActions } from 'redux-actions'; - -import { - RepositoryUri, - WorkerProgress, - WorkerReservedProgress, - IndexWorkerProgress, - CloneWorkerProgress, - RepoState, -} from '../../model'; -import { - deleteRepoFinished, - FetchFilePayload, - FetchRepoFileStatusSuccess, - loadStatus, - loadStatusFailed, - loadStatusSuccess, - updateCloneProgress, - updateDeleteProgress, - updateIndexProgress, - StatusSuccessPayload, - RepoStatus, - FetchRepoFileStatus, -} from '../actions'; -import { StatusReport } from '../../common/repo_file_status'; - -export interface StatusState { - status: { [key: string]: RepoStatus }; - loading: boolean; - error?: Error; - currentStatusPath?: FetchFilePayload; - repoFileStatus?: StatusReport; -} - -const initialState: StatusState = { - status: {}, - loading: false, -}; - -type StatusPayload = RepoStatus & Error & StatusSuccessPayload & string; - -const getIndexState = (indexStatus: IndexWorkerProgress) => { - const progress = indexStatus.progress; - if ( - progress === WorkerReservedProgress.ERROR || - progress === WorkerReservedProgress.TIMEOUT || - progress < WorkerReservedProgress.INIT - ) { - return RepoState.INDEX_ERROR; - } else if (progress < WorkerReservedProgress.COMPLETED) { - return RepoState.INDEXING; - } else if (progress === WorkerReservedProgress.COMPLETED) { - return RepoState.READY; - } - return RepoState.UNKNOWN; -}; - -const getGitState = (gitStatus: CloneWorkerProgress) => { - const progress = gitStatus.progress; - if ( - progress === WorkerReservedProgress.ERROR || - progress === WorkerReservedProgress.TIMEOUT || - progress < WorkerReservedProgress.INIT - ) { - return RepoState.CLONE_ERROR; - } else if (progress < WorkerReservedProgress.COMPLETED) { - if (gitStatus.cloneProgress && gitStatus.cloneProgress.isCloned) { - return RepoState.UPDATING; - } - return RepoState.CLONING; - } else if (progress === WorkerReservedProgress.COMPLETED) { - return RepoState.READY; - } - return RepoState.UNKNOWN; -}; - -const getDeleteState = (deleteStatus: WorkerProgress) => { - const progress = deleteStatus.progress; - if ( - progress === WorkerReservedProgress.ERROR || - progress === WorkerReservedProgress.TIMEOUT || - progress < WorkerReservedProgress.INIT - ) { - return RepoState.DELETE_ERROR; - } else if (progress < WorkerReservedProgress.COMPLETED) { - return RepoState.DELETING; - } else { - return RepoState.UNKNOWN; - } -}; - -export const status = handleActions( - { - [String(loadStatus)]: state => - produce(state, draft => { - draft.loading = true; - }), - [String(loadStatusSuccess)]: (state, action: Action) => - produce(state, draft => { - draft.loading = false; - Object.keys(action.payload!).forEach((repoUri: RepositoryUri) => { - const statuses = action.payload![repoUri]!; - if (statuses.deleteStatus) { - // 1. Look into delete status first - const deleteState = getDeleteState(statuses.deleteStatus); - draft.status[repoUri] = { - ...statuses.deleteStatus, - state: deleteState, - }; - return; - } else { - // 2. Then take the index state and git clone state into - // account in the meantime. - let indexState = RepoState.UNKNOWN; - if (statuses.indexStatus) { - indexState = getIndexState(statuses.indexStatus); - } - let gitState = RepoState.UNKNOWN; - if (statuses.gitStatus) { - gitState = getGitState(statuses.gitStatus); - } - - if (statuses.gitStatus) { - // Git state has higher priority over index state - if (gitState === RepoState.CLONING || gitState === RepoState.CLONE_ERROR) { - draft.status[repoUri] = { - ...statuses.gitStatus, - state: gitState, - }; - return; - } else if (gitState === RepoState.READY && !statuses.indexStatus) { - // If git state is ready and index status is not there, then return - // the git status - draft.status[repoUri] = { - ...statuses.gitStatus, - state: gitState, - }; - return; - } - } - - if (statuses.indexStatus) { - draft.status[repoUri] = { - ...statuses.indexStatus, - state: indexState, - }; - return; - } - - // If non of delete/git/index status exists, then do nothing here. - } - }); - }), - [String(loadStatusFailed)]: (state, action: Action) => - produce(state, draft => { - draft.loading = false; - draft.error = action.payload; - }), - [String(updateCloneProgress)]: (state, action: Action) => - produce(state, draft => { - const progress = action.payload!.progress; - let s = RepoState.CLONING; - if ( - progress === WorkerReservedProgress.ERROR || - progress === WorkerReservedProgress.TIMEOUT - ) { - s = RepoState.CLONE_ERROR; - } else if (progress === WorkerReservedProgress.COMPLETED) { - s = RepoState.READY; - } - draft.status[action.payload!.uri] = { - ...action.payload!, - state: s, - }; - }), - [String(updateIndexProgress)]: (state, action: Action) => - produce(state, draft => { - const progress = action.payload!.progress; - let s = RepoState.INDEXING; - if ( - progress === WorkerReservedProgress.ERROR || - progress === WorkerReservedProgress.TIMEOUT - ) { - s = RepoState.INDEX_ERROR; - } else if (progress === WorkerReservedProgress.COMPLETED) { - s = RepoState.READY; - } - draft.status[action.payload!.uri] = { - ...action.payload!, - state: s, - }; - }), - [String(updateDeleteProgress)]: (state, action: Action) => - produce(state, draft => { - const progress = action.payload!.progress; - if (progress === WorkerReservedProgress.COMPLETED) { - delete draft.status[action.payload!.uri]; - } else { - let s = RepoState.DELETING; - if ( - progress === WorkerReservedProgress.ERROR || - progress === WorkerReservedProgress.TIMEOUT - ) { - s = RepoState.DELETE_ERROR; - } - - draft.status[action.payload!.uri] = { - ...action.payload!, - state: s, - }; - } - }), - [String(deleteRepoFinished)]: (state, action: Action) => - produce(state, draft => { - delete draft.status[action.payload!]; - }), - [String(FetchRepoFileStatusSuccess)]: (state: StatusState, action: Action) => - produce(state, (draft: StatusState) => { - const { path, statusReport } = action.payload; - draft.repoFileStatus = statusReport; - draft.currentStatusPath = path; - }), - [String(FetchRepoFileStatus)]: (state: StatusState, action: Action) => - produce(state, (draft: StatusState) => { - draft.currentStatusPath = action.payload; - }), - }, - initialState -); diff --git a/x-pack/legacy/plugins/code/public/reducers/symbol.ts b/x-pack/legacy/plugins/code/public/reducers/symbol.ts deleted file mode 100644 index 33d92953c69a7..0000000000000 --- a/x-pack/legacy/plugins/code/public/reducers/symbol.ts +++ /dev/null @@ -1,106 +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 produce from 'immer'; -import _ from 'lodash'; -import { Action, handleActions } from 'redux-actions'; - -import { DocumentSymbol } from 'vscode-languageserver-types'; -import { - closeSymbolPath, - loadStructure, - loadStructureFailed, - loadStructureSuccess, - openSymbolPath, - SymbolsPayload, - SymbolWithMembers, -} from '../actions'; -import { languageServerInitializing } from '../actions/language_server'; -import { routePathChange, repoChange, revisionChange, filePathChange } from '../actions/route'; - -export interface SymbolState { - symbols: { [key: string]: DocumentSymbol[] }; - structureTree: { [key: string]: SymbolWithMembers[] }; - error?: Error; - loading: boolean; - lastRequestPath?: string; - closedPaths: string[]; - languageServerInitializing: boolean; -} - -const initialState: SymbolState = { - symbols: {}, - loading: false, - structureTree: {}, - closedPaths: [], - languageServerInitializing: false, -}; - -const clearState = (state: SymbolState) => - produce(state, draft => { - draft.symbols = initialState.symbols; - draft.loading = initialState.loading; - draft.structureTree = initialState.structureTree; - draft.closedPaths = initialState.closedPaths; - draft.languageServerInitializing = initialState.languageServerInitializing; - draft.error = undefined; - draft.lastRequestPath = undefined; - }); - -type SymbolPayload = string & SymbolsPayload & Error; - -export const symbol = handleActions( - { - [String(loadStructure)]: (state, action: Action) => - produce(state, draft => { - draft.loading = true; - draft.lastRequestPath = action.payload || ''; - }), - [String(loadStructureSuccess)]: (state, action: Action) => - produce(state, draft => { - draft.loading = false; - const { path, data, structureTree } = action.payload!; - draft.structureTree[path] = structureTree; - draft.symbols = { - ...state.symbols, - [path]: data, - }; - draft.languageServerInitializing = false; - draft.error = undefined; - }), - [String(loadStructureFailed)]: (state, action: Action) => - produce(state, draft => { - if (action.payload) { - draft.loading = false; - draft.error = action.payload; - } - draft.languageServerInitializing = false; - }), - [String(closeSymbolPath)]: (state, action: Action) => - produce(state, draft => { - const path = action.payload!; - if (!state.closedPaths.includes(path)) { - draft.closedPaths.push(path); - } - }), - [String(openSymbolPath)]: (state, action: Action) => - produce(state, draft => { - const idx = state.closedPaths.indexOf(action.payload!); - if (idx >= 0) { - draft.closedPaths.splice(idx, 1); - } - }), - [String(languageServerInitializing)]: state => - produce(state, draft => { - draft.languageServerInitializing = true; - }), - [String(routePathChange)]: clearState, - [String(repoChange)]: clearState, - [String(revisionChange)]: clearState, - [String(filePathChange)]: clearState, - }, - initialState -); diff --git a/x-pack/legacy/plugins/code/public/sagas/blame.ts b/x-pack/legacy/plugins/code/public/sagas/blame.ts deleted file mode 100644 index c5a39d4931137..0000000000000 --- a/x-pack/legacy/plugins/code/public/sagas/blame.ts +++ /dev/null @@ -1,42 +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 { Action } from 'redux-actions'; -import { npStart } from 'ui/new_platform'; -import { call, put, takeEvery } from 'redux-saga/effects'; -import { Match } from '../actions'; -import { loadBlame, loadBlameFailed, LoadBlamePayload, loadBlameSuccess } from '../actions/blame'; -import { blamePattern } from './patterns'; - -export function requestBlame(repoUri: string, revision: string, path: string) { - return npStart.core.http.get( - `/api/code/repo/${repoUri}/blame/${encodeURIComponent(revision)}/${path}` - ); -} - -function* handleFetchBlame(action: Action) { - try { - const { repoUri, revision, path } = action.payload!; - const blame = yield call(requestBlame, repoUri, revision, path); - yield put(loadBlameSuccess(blame)); - } catch (err) { - yield put(loadBlameFailed(err)); - } -} - -export function* watchLoadBlame() { - yield takeEvery(String(loadBlame), handleFetchBlame); -} - -function* handleBlame(action: Action) { - const { resource, org, repo, revision, path } = action.payload!.params; - const repoUri = `${resource}/${org}/${repo}`; - yield put(loadBlame({ repoUri, revision, path })); -} - -export function* watchBlame() { - yield takeEvery(blamePattern, handleBlame); -} diff --git a/x-pack/legacy/plugins/code/public/sagas/commit.ts b/x-pack/legacy/plugins/code/public/sagas/commit.ts deleted file mode 100644 index 93a5c74e19c4a..0000000000000 --- a/x-pack/legacy/plugins/code/public/sagas/commit.ts +++ /dev/null @@ -1,30 +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 { Action } from 'redux-actions'; -import { npStart } from 'ui/new_platform'; -import { call, put, takeEvery } from 'redux-saga/effects'; -import { loadCommit, loadCommitFailed, loadCommitSuccess, Match } from '../actions'; -import { commitRoutePattern } from './patterns'; - -function requestCommit(repo: string, commitId: string) { - return npStart.core.http.get(`/api/code/repo/${repo}/diff/${commitId}`); -} - -function* handleLoadCommit(action: Action) { - try { - const { commitId, resource, org, repo } = action.payload!.params; - yield put(loadCommit(commitId)); - const repoUri = `${resource}/${org}/${repo}`; - const commit = yield call(requestCommit, repoUri, commitId); - yield put(loadCommitSuccess(commit)); - } catch (err) { - yield put(loadCommitFailed(err)); - } -} - -export function* watchLoadCommit() { - yield takeEvery(commitRoutePattern, handleLoadCommit); -} diff --git a/x-pack/legacy/plugins/code/public/sagas/editor.ts b/x-pack/legacy/plugins/code/public/sagas/editor.ts deleted file mode 100644 index e00710d241260..0000000000000 --- a/x-pack/legacy/plugins/code/public/sagas/editor.ts +++ /dev/null @@ -1,264 +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 queryString from 'querystring'; -import { Action } from 'redux-actions'; -import { npStart } from 'ui/new_platform'; -import { TextDocumentPositionParams } from 'vscode-languageserver'; -import Url from 'url'; -import { call, put, select, takeEvery, takeLatest } from 'redux-saga/effects'; -import { parseGoto, parseLspUrl, toCanonicalUrl } from '../../common/uri_util'; -import { FileTree } from '../../model'; -import { - fetchFile, - FetchFileResponse, - fetchRepoTree, - fetchTreeCommits, - findReferences, - findReferencesFailed, - findReferencesSuccess, - loadStructure, - Match, - revealPosition, - fetchRepos, - turnOnDefaultRepoScope, - findDefinitions, - findDefinitionsSuccess, - findDefinitionsFailed, - closePanel, -} from '../actions'; -import { loadRepo, loadRepoFailed, loadRepoSuccess } from '../actions/status'; -import { PathTypes } from '../common/types'; -import { RootState } from '../reducers'; -import { getPathOfTree } from '../reducers/file_tree'; -import { - fileSelector, - getTree, - lastRequestPathSelector, - refUrlSelector, - repoScopeSelector, - urlQueryStringSelector, - createTreeSelector, - reposSelector, -} from '../selectors'; -import { history } from '../utils/url'; -import { mainRoutePattern } from './patterns'; - -function* handleReferences(action: Action) { - try { - const params: TextDocumentPositionParams = action.payload!; - const { title, files } = yield call(requestFindReferences, params); - const repos = Object.keys(files).map((repo: string) => ({ repo, files: files[repo] })); - yield put(findReferencesSuccess({ title, repos })); - } catch (error) { - yield put(findReferencesFailed(error)); - } -} - -function* handleDefinitions(action: Action) { - try { - const params: TextDocumentPositionParams = action.payload!; - const { title, files } = yield call(requestFindDefinitions, params); - const repos = Object.keys(files).map((repo: string) => ({ repo, files: files[repo] })); - yield put(findDefinitionsSuccess({ title, repos })); - } catch (error) { - yield put(findDefinitionsFailed(error)); - } -} - -function requestFindReferences(params: TextDocumentPositionParams) { - return npStart.core.http.post(`/api/code/lsp/findReferences`, { - body: JSON.stringify(params), - }); -} - -function requestFindDefinitions(params: TextDocumentPositionParams) { - return npStart.core.http.post(`/api/code/lsp/findDefinitions`, { - body: JSON.stringify(params), - }); -} - -export function* watchLspMethods() { - yield takeLatest(String(findReferences), handleReferences); - yield takeLatest(String(findDefinitions), handleDefinitions); -} - -function* handleClosePanel(action: Action) { - if (action.payload) { - const search = yield select(urlQueryStringSelector); - const { pathname } = history.location; - const queryParams = Url.parse(search, true).query; - if (queryParams.tab) { - delete queryParams.tab; - } - if (queryParams.refUrl) { - delete queryParams.refUrl; - } - const query = queryString.stringify(queryParams); - if (query) { - history.push(`${pathname}?${query}`); - } else { - history.push(pathname); - } - } -} - -export function* watchCloseReference() { - yield takeLatest(String(closePanel), handleClosePanel); -} - -function* handleReference(url: string) { - const refUrl = yield select(refUrlSelector); - if (refUrl === url) { - return; - } - const { uri, position, schema, repoUri, file, revision } = parseLspUrl(url); - if (uri && position) { - yield put( - findReferences({ - textDocument: { - uri: toCanonicalUrl({ revision, schema, repoUri, file }), - }, - position, - }) - ); - } -} - -function* openDefinitions(url: string) { - const refUrl = yield select(refUrlSelector); - if (refUrl === url) { - return; - } - const { uri, position, schema, repoUri, file, revision } = parseLspUrl(url); - if (uri && position) { - yield put( - findDefinitions({ - textDocument: { - uri: toCanonicalUrl({ revision, schema, repoUri, file }), - }, - position, - }) - ); - } -} - -function* handleFile(repoUri: string, file: string, revision: string) { - const response: FetchFileResponse = yield select(fileSelector); - const payload = response && response.payload; - if ( - payload && - payload.path === file && - payload.revision === revision && - payload.uri === repoUri - ) { - return; - } - yield put( - fetchFile({ - uri: repoUri, - path: file, - revision, - }) - ); -} - -function fetchRepo(repoUri: string) { - return npStart.core.http.get(`/api/code/repo/${repoUri}`); -} - -function* loadRepoSaga(action: any) { - try { - const repo = yield call(fetchRepo, action.payload); - yield put(loadRepoSuccess(repo)); - - // turn on defaultRepoScope if there's no repo scope specified when enter a source view page - const repoScope = yield select(repoScopeSelector); - if (repoScope.length === 0) { - yield put(turnOnDefaultRepoScope(repo)); - } - } catch (e) { - yield put(loadRepoFailed(e)); - } -} - -export function* watchLoadRepo() { - yield takeEvery(String(loadRepo), loadRepoSaga); -} - -function* handleMainRouteChange(action: Action) { - const repos = yield select(reposSelector); - if (repos.length === 0) { - // in source view page, we need repos as default repo scope options when no query input - yield put(fetchRepos()); - } - const { location } = action.payload!; - const search = location.search.startsWith('?') ? location.search.substring(1) : location.search; - const queryParams = queryString.parse(search); - const { resource, org, repo, path: file, pathType, revision, goto } = action.payload!.params; - const repoUri = `${resource}/${org}/${repo}`; - let position; - if (goto) { - position = parseGoto(goto); - } - if (file) { - if ([PathTypes.blob, PathTypes.blame].includes(pathType as PathTypes)) { - yield put(revealPosition(position)); - const { tab, refUrl } = queryParams; - if (tab === 'references' && refUrl) { - yield call(handleReference, decodeURIComponent(refUrl as string)); - } else if (tab === 'definitions' && refUrl) { - yield call(openDefinitions, decodeURIComponent(refUrl as string)); - } else { - yield put(closePanel(false)); - } - yield call(handleFile, repoUri, file, revision); - } else { - const commits = yield select((state: RootState) => state.revision.treeCommits[file]); - if (commits === undefined) { - yield put(fetchTreeCommits({ revision, uri: repoUri, path: file })); - } - } - } - const lastRequestPath = yield select(lastRequestPathSelector); - const tree = yield select(getTree); - const isDir = pathType === PathTypes.tree; - function isTreeLoaded(isDirectory: boolean, targetTree: FileTree | null) { - if (!isDirectory) { - return !!targetTree; - } else if (!targetTree) { - return false; - } else { - return targetTree.children && targetTree.children.length > 0; - } - } - const targetTree: FileTree | null = yield select(createTreeSelector(file || '')); - if (!isTreeLoaded(isDir, targetTree)) { - yield put( - fetchRepoTree({ - uri: repoUri, - revision, - path: file || '', - parents: getPathOfTree(tree, (file || '').split('/')) === null, - isDir, - }) - ); - } - const uri = toCanonicalUrl({ - repoUri, - file, - revision, - }); - if (file && pathType === PathTypes.blob) { - if (lastRequestPath !== uri) { - yield put(loadStructure(uri!)); - } - } -} - -export function* watchMainRouteChange() { - yield takeLatest(mainRoutePattern, handleMainRouteChange); -} diff --git a/x-pack/legacy/plugins/code/public/sagas/file.ts b/x-pack/legacy/plugins/code/public/sagas/file.ts deleted file mode 100644 index 42bb44d266306..0000000000000 --- a/x-pack/legacy/plugins/code/public/sagas/file.ts +++ /dev/null @@ -1,299 +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 { Action } from 'redux-actions'; -import { npStart } from 'ui/new_platform'; -import Url from 'url'; -import { call, put, select, takeEvery, takeLatest, cancel } from 'redux-saga/effects'; - -import { - fetchDirectory, - fetchDirectoryFailed, - fetchDirectorySuccess, - fetchFile, - fetchFileFailed, - FetchFilePayload, - FetchFileResponse, - fetchFileSuccess, - fetchMoreCommits, - fetchRepoBranches, - fetchRepoBranchesFailed, - fetchRepoBranchesSuccess, - fetchRepoCommits, - fetchRepoCommitsFailed, - fetchRepoCommitsSuccess, - FetchRepoPayload, - FetchRepoPayloadWithRevision, - fetchRepoTree, - fetchRepoTreeFailed, - FetchRepoTreePayload, - fetchRepoTreeSuccess, - fetchTreeCommits, - fetchTreeCommitsFailed, - fetchTreeCommitsSuccess, - gotoRepo, - Match, - setNotFound, - fetchRootRepoTree, - fetchRootRepoTreeSuccess, - fetchRootRepoTreeFailed, - dirNotFound, -} from '../actions'; -import { treeCommitsSelector, currentPathSelector } from '../selectors'; -import { repoRoutePattern } from './patterns'; -import { FileTree } from '../../model'; -import { singletonRequestSaga } from '../utils/saga_utils'; - -function* handleFetchRepoTree(action: Action) { - try { - const tree = yield call(requestRepoTree, action.payload!); - (tree.children || []).sort((a: FileTree, b: FileTree) => { - const typeDiff = a.type - b.type; - if (typeDiff === 0) { - return a.name > b.name ? 1 : -1; - } else { - return -typeDiff; - } - }); - tree.repoUri = action.payload!.uri; - yield put( - fetchRepoTreeSuccess({ - revision: action.payload!.revision, - tree, - path: action.payload!.path, - withParents: action.payload!.parents, - }) - ); - } catch (err) { - if (action.payload!.isDir && err.body && err.body.statusCode === 404) { - yield put(dirNotFound(action.payload!.path)); - } - yield put(fetchRepoTreeFailed({ ...err, path: action.payload!.path })); - } -} - -interface FileTreeQuery { - parents?: boolean; - limit: number; - flatten: boolean; - [key: string]: string | number | boolean | undefined; -} - -function requestRepoTree({ - uri, - revision, - path, - limit = 1000, - parents = false, -}: FetchRepoTreePayload) { - const query: FileTreeQuery = { limit, flatten: true }; - if (parents) { - query.parents = true; - } - return npStart.core.http.get( - `/api/code/repo/${uri}/tree/${encodeURIComponent(revision)}/${path}`, - { - query, - } - ); -} - -export function* watchFetchRepoTree() { - yield takeEvery(String(fetchRepoTree), handleFetchRepoTree); -} - -function* handleFetchRootRepoTree(action: Action) { - try { - const { uri, revision } = action.payload!; - const tree = yield call(requestRepoTree, { uri, revision, path: '', isDir: true }); - yield put(fetchRootRepoTreeSuccess({ tree, revision })); - } catch (err) { - yield put(fetchRootRepoTreeFailed(err)); - } -} - -export function* watchFetchRootRepoTree() { - yield takeEvery(String(fetchRootRepoTree), handleFetchRootRepoTree); -} - -function* handleFetchBranches(action: Action) { - try { - const results = yield call(requestBranches, action.payload!); - yield put(fetchRepoBranchesSuccess(results)); - } catch (err) { - yield put(fetchRepoBranchesFailed(err)); - } -} - -function requestBranches({ uri }: FetchRepoPayload) { - return npStart.core.http.get(`/api/code/repo/${uri}/references`); -} - -function* handleFetchCommits(action: Action) { - try { - const results = yield call(requestCommits, action.payload!); - yield put(fetchRepoCommitsSuccess(results)); - } catch (err) { - yield put(fetchRepoCommitsFailed(err)); - } -} - -function* handleFetchMoreCommits(action: Action) { - try { - const path = yield select(currentPathSelector); - const commits = yield select(treeCommitsSelector); - const revision = commits.length > 0 ? commits[commits.length - 1].id : 'head'; - const uri = action.payload; - // @ts-ignore - const newCommits = yield call(requestCommits, { uri, revision }, path, true); - yield put(fetchTreeCommitsSuccess({ path, commits: newCommits, append: true })); - } catch (err) { - yield put(fetchTreeCommitsFailed(err)); - } -} - -function* handleFetchTreeCommits(action: Action, signal: AbortSignal, task: any) { - try { - const path = action.payload!.path; - const commits = yield call(requestCommits, action.payload!, path, undefined, undefined, signal); - yield put(fetchTreeCommitsSuccess({ path, commits })); - } catch (err) { - yield put(fetchTreeCommitsFailed(err)); - } finally { - yield cancel(task); - } -} - -export function requestCommits( - { uri, revision }: FetchRepoPayloadWithRevision, - path?: string, - loadMore?: boolean, - count?: number, - signal?: AbortSignal -) { - const pathStr = path ? `/${path}` : ''; - let query: any = {}; - if (loadMore) { - query = { after: 1 }; - } - if (count) { - query = { count }; - } - return npStart.core.http.get( - `/api/code/repo/${uri}/history/${encodeURIComponent(revision)}${pathStr}`, - { - query, - signal, - } - ); -} - -export async function requestFile( - payload: FetchFilePayload, - line?: string, - signal?: AbortSignal -): Promise { - const { uri, revision, path } = payload; - const url = `/api/code/repo/${uri}/blob/${encodeURIComponent(revision)}/${path}`; - const query: any = {}; - if (line) { - query.line = line; - } - const response: Response = await fetch( - npStart.core.http.basePath.prepend(Url.format({ pathname: url, query })), - { signal } - ); - - if (response.status >= 200 && response.status < 300) { - const contentType = response.headers.get('Content-Type'); - - if (contentType && contentType.startsWith('text/')) { - const lang = response.headers.get('lang') || undefined; - if (lang === 'big') { - return { - payload, - content: '', - isOversize: true, - }; - } - return { - payload, - lang, - content: await response.text(), - isUnsupported: false, - }; - } else if (contentType && contentType.startsWith('image/')) { - return { - payload, - isImage: true, - content: '', - url, - isUnsupported: false, - }; - } else { - return { - payload, - isImage: false, - content: '', - url, - isUnsupported: true, - }; - } - } else if (response.status === 404) { - return { - payload, - isNotFound: true, - }; - } - throw new Error('invalid file type'); -} - -function* handleFetchFile(action: Action, signal: AbortSignal, task: any) { - try { - const results = yield call(requestFile, action.payload!, undefined, signal); - if (results.isNotFound) { - yield put(setNotFound(true)); - yield put(fetchFileFailed(new Error('file not found'))); - } else { - const path = yield select(currentPathSelector); - if (path === action.payload!.path) { - yield put(fetchFileSuccess(results)); - } - } - } catch (err) { - yield put(fetchFileFailed(err)); - } finally { - yield cancel(task); - } -} - -function* handleFetchDirs(action: Action) { - try { - const dir = yield call(requestRepoTree, action.payload!); - yield put(fetchDirectorySuccess(dir)); - } catch (err) { - yield fetchDirectoryFailed(err); - } -} - -export function* watchFetchBranchesAndCommits() { - yield takeEvery(String(fetchRepoBranches), handleFetchBranches); - yield takeEvery(String(fetchRepoCommits), handleFetchCommits); - yield takeLatest(String(fetchFile), singletonRequestSaga(handleFetchFile)); - yield takeEvery(String(fetchDirectory), handleFetchDirs); - yield takeLatest(String(fetchTreeCommits), singletonRequestSaga(handleFetchTreeCommits)); - yield takeLatest(String(fetchMoreCommits), handleFetchMoreCommits); -} - -function* handleRepoRouteChange(action: Action) { - const { repo, org, resource } = action.payload!.params; - const uri = `${resource}/${org}/${repo}`; - yield put(gotoRepo(uri)); -} - -export function* watchRepoRouteChange() { - yield takeEvery(repoRoutePattern, handleRepoRouteChange); -} diff --git a/x-pack/legacy/plugins/code/public/sagas/index.ts b/x-pack/legacy/plugins/code/public/sagas/index.ts deleted file mode 100644 index 0b6e33de064f8..0000000000000 --- a/x-pack/legacy/plugins/code/public/sagas/index.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 { fork } from 'redux-saga/effects'; - -import { watchBlame, watchLoadBlame } from './blame'; -import { watchLoadCommit } from './commit'; -import { - watchCloseReference, - watchLoadRepo, - watchLspMethods, - watchMainRouteChange, -} from './editor'; -import { - watchFetchBranchesAndCommits, - watchFetchRepoTree, - watchRepoRouteChange, - watchFetchRootRepoTree, -} from './file'; -import { watchInstallLanguageServer, watchLoadLanguageServers } from './language_server'; -import { - watchLoadRepoListStatus, - watchLoadRepoStatus, - watchPollingRepoStatus, - watchRepoCloneStatusPolling, - watchRepoDeleteStatusPolling, - watchRepoIndexStatusPolling, - watchResetPollingStatus, -} from './project_status'; -import { - watchAdminRouteChange, - watchDeleteRepo, - watchFetchRepoConfigs, - watchFetchRepos, - watchGotoRepo, - watchImportRepo, - watchIndexRepo, - watchInitRepoCmd, -} from './repository'; -import { - watchDocumentSearch, - watchRepoScopeSearch, - watchRepositorySearch, - watchSearchRouteChange, -} from './search'; -import { watchRootRoute } from './setup'; -import { watchRepoCloneSuccess, watchRepoDeleteFinished, watchStatusChange } from './status'; -import { watchLoadStructure } from './structure'; -import { watchRoute, watchRepoChange, watchRepoOrRevisionChange } from './route'; - -export function* rootSaga() { - yield fork(watchRepoChange); - yield fork(watchRepoOrRevisionChange); - yield fork(watchRoute); - yield fork(watchRootRoute); - yield fork(watchLoadCommit); - yield fork(watchFetchRepos); - yield fork(watchDeleteRepo); - yield fork(watchIndexRepo); - yield fork(watchImportRepo); - yield fork(watchFetchRepoTree); - yield fork(watchFetchRootRepoTree); - yield fork(watchFetchBranchesAndCommits); - yield fork(watchDocumentSearch); - yield fork(watchRepositorySearch); - yield fork(watchLoadStructure); - yield fork(watchLspMethods); - yield fork(watchCloseReference); - yield fork(watchFetchRepoConfigs); - yield fork(watchInitRepoCmd); - yield fork(watchGotoRepo); - yield fork(watchLoadRepo); - yield fork(watchSearchRouteChange); - yield fork(watchAdminRouteChange); - yield fork(watchMainRouteChange); - yield fork(watchLoadRepo); - yield fork(watchRepoRouteChange); - yield fork(watchLoadBlame); - yield fork(watchBlame); - yield fork(watchRepoCloneSuccess); - yield fork(watchRepoDeleteFinished); - yield fork(watchLoadLanguageServers); - yield fork(watchInstallLanguageServer); - yield fork(watchLoadRepoListStatus); - yield fork(watchLoadRepoStatus); - yield fork(watchStatusChange); - - // Repository status polling sagas begin - yield fork(watchPollingRepoStatus); - yield fork(watchResetPollingStatus); - yield fork(watchRepoDeleteStatusPolling); - yield fork(watchRepoIndexStatusPolling); - yield fork(watchRepoCloneStatusPolling); - // Repository status polling sagas end - - yield fork(watchRepoScopeSearch); -} diff --git a/x-pack/legacy/plugins/code/public/sagas/language_server.ts b/x-pack/legacy/plugins/code/public/sagas/language_server.ts deleted file mode 100644 index c9e452201aa40..0000000000000 --- a/x-pack/legacy/plugins/code/public/sagas/language_server.ts +++ /dev/null @@ -1,51 +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 { Action } from 'redux-actions'; -import { npStart } from 'ui/new_platform'; -import { call, put, takeEvery } from 'redux-saga/effects'; -import { - loadLanguageServers, - loadLanguageServersFailed, - loadLanguageServersSuccess, - requestInstallLanguageServer, - requestInstallLanguageServerFailed, - requestInstallLanguageServerSuccess, -} from '../actions/language_server'; - -function fetchLangServers() { - return npStart.core.http.get('/api/code/install'); -} - -function installLanguageServer(languageServer: string) { - return npStart.core.http.post(`/api/code/install/${languageServer}`); -} - -function* handleInstallLanguageServer(action: Action) { - try { - yield call(installLanguageServer, action.payload!); - yield put(requestInstallLanguageServerSuccess(action.payload!)); - } catch (err) { - yield put(requestInstallLanguageServerFailed(err)); - } -} - -function* handleLoadLanguageServer() { - try { - const langServers = yield call(fetchLangServers); - yield put(loadLanguageServersSuccess(langServers)); - } catch (err) { - yield put(loadLanguageServersFailed(err)); - } -} - -export function* watchLoadLanguageServers() { - yield takeEvery(String(loadLanguageServers), handleLoadLanguageServer); -} - -export function* watchInstallLanguageServer() { - yield takeEvery(String(requestInstallLanguageServer), handleInstallLanguageServer); -} diff --git a/x-pack/legacy/plugins/code/public/sagas/patterns.ts b/x-pack/legacy/plugins/code/public/sagas/patterns.ts deleted file mode 100644 index a2f1cac8212a8..0000000000000 --- a/x-pack/legacy/plugins/code/public/sagas/patterns.ts +++ /dev/null @@ -1,28 +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 { Action } from 'redux-actions'; - -import { Match, routeChange } from '../actions'; -import { PathTypes } from '../common/types'; -import * as ROUTES from '../components/routes'; - -export const generatePattern = (path: string) => (action: Action) => - action.type === String(routeChange) && action.payload!.path === path; -export const rootRoutePattern = generatePattern(ROUTES.ROOT); -export const setupRoutePattern = generatePattern(ROUTES.SETUP); -export const adminRoutePattern = generatePattern(ROUTES.ADMIN); -export const repoRoutePattern = generatePattern(ROUTES.REPO); -export const mainRoutePattern = (action: Action) => - action.type === String(routeChange) && - (ROUTES.MAIN === action.payload!.path || ROUTES.MAIN_ROOT === action.payload!.path); -export const searchRoutePattern = generatePattern(ROUTES.SEARCH); -export const commitRoutePattern = generatePattern(ROUTES.DIFF); - -export const sourceFilePattern = (action: Action) => - mainRoutePattern(action) && action.payload!.params.pathType === PathTypes.blob; - -export const blamePattern = (action: Action) => - mainRoutePattern(action) && action.payload!.params.pathType === PathTypes.blame; diff --git a/x-pack/legacy/plugins/code/public/sagas/project_config.ts b/x-pack/legacy/plugins/code/public/sagas/project_config.ts deleted file mode 100644 index c417912f9da58..0000000000000 --- a/x-pack/legacy/plugins/code/public/sagas/project_config.ts +++ /dev/null @@ -1,64 +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 { Action } from 'redux-actions'; -import { npStart } from 'ui/new_platform'; -import { all, call, put, takeEvery } from 'redux-saga/effects'; -import { Repository, RepositoryConfig } from '../../model'; -import { - fetchReposSuccess, - RepoConfigPayload, - switchLanguageServer, - switchLanguageServerFailed, - switchLanguageServerSuccess, -} from '../actions'; -import { loadConfigsFailed, loadConfigsSuccess } from '../actions/project_config'; - -function putProjectConfig(repoUri: string, config: RepositoryConfig) { - return npStart.core.http.put(`/api/code/repo/config/${repoUri}`, { - body: JSON.stringify(config), - }); -} - -function* switchProjectLanguageServer(action: Action) { - try { - const { repoUri, config } = action.payload!; - yield call(putProjectConfig, repoUri, config); - yield put(switchLanguageServerSuccess()); - } catch (err) { - yield put(switchLanguageServerFailed(err)); - } -} - -export function* watchSwitchProjectLanguageServer() { - yield takeEvery(String(switchLanguageServer), switchProjectLanguageServer); -} - -function fetchConfigs(repoUri: string) { - return npStart.core.http.get(`/api/code/repo/config/${repoUri}`); -} - -function* loadConfigs(action: Action) { - try { - const repositories = action.payload!; - const promises = repositories.map(repo => call(fetchConfigs, repo.uri)); - const configs = yield all(promises); - yield put( - loadConfigsSuccess( - configs.reduce((acc: { [k: string]: RepositoryConfig }, config: RepositoryConfig) => { - acc[config.uri] = config; - return acc; - }, {}) - ) - ); - } catch (err) { - yield put(loadConfigsFailed(err)); - } -} - -export function* watchLoadConfigs() { - yield takeEvery(String(fetchReposSuccess), loadConfigs); -} diff --git a/x-pack/legacy/plugins/code/public/sagas/project_status.ts b/x-pack/legacy/plugins/code/public/sagas/project_status.ts deleted file mode 100644 index ed4b95d612182..0000000000000 --- a/x-pack/legacy/plugins/code/public/sagas/project_status.ts +++ /dev/null @@ -1,390 +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 moment from 'moment'; -import { Action } from 'redux-actions'; -import { delay } from 'redux-saga'; -import { npStart } from 'ui/new_platform'; -import { - all, - call, - cancel, - cancelled, - fork, - put, - select, - take, - takeEvery, - takeLatest, -} from 'redux-saga/effects'; - -import { Repository, RepositoryUri, WorkerReservedProgress, RepoState } from '../../model'; -import * as ROUTES from '../components/routes'; -import { allStatusSelector, repoUriSelector, routeSelector } from '../selectors'; -import { - deleteRepo, - fetchReposSuccess, - indexRepo, - loadRepoSuccess, - loadStatusFailed, - loadStatusSuccess, - pollRepoCloneStatusStart, - pollRepoDeleteStatusStart, - pollRepoIndexStatusStart, - routeChange, - updateCloneProgress, - updateDeleteProgress, - updateIndexProgress, - pollRepoCloneStatusStop, - pollRepoDeleteStatusStop, - pollRepoIndexStatusStop, - importRepoSuccess, -} from '../actions'; -import { - cloneCompletedPattern, - cloneRepoStatusPollingStopPattern, - deleteRepoStatusPollingStopPattern, - indexRepoStatusPollingStopPattern, -} from './status'; - -const REPO_STATUS_POLLING_FREQ_MS = 1000; - -function fetchStatus(repoUri: string) { - return npStart.core.http.get(`/api/code/repo/status/${repoUri}`); -} - -function* loadRepoListStatus(repos: Repository[]) { - try { - const promises = repos.map(repo => call(fetchStatus, repo.uri)); - const statuses = yield all(promises); - yield put( - loadStatusSuccess( - statuses.reduce((acc: { [k: string]: any }, status: any) => { - acc[status.gitStatus.uri] = status; - return acc; - }, {}) - ) - ); - } catch (err) { - yield put(loadStatusFailed(err)); - } -} - -function* loadRepoStatus(repo: Repository) { - try { - const repoStatus = yield call(fetchStatus, repo.uri); - yield put( - loadStatusSuccess({ - [repo.uri]: repoStatus, - }) - ); - } catch (err) { - yield put(loadStatusFailed(err)); - } -} - -function* handleRepoStatus(action: Action) { - const repository: Repository = action.payload!; - yield call(loadRepoStatus, repository); -} - -function* handleRepoListStatus(action: Action) { - const route = yield select(routeSelector); - if (route.path === ROUTES.ADMIN) { - // Only load repository list status in admin page, because in source view - // page, we also need to load all repositories but not their status. - const repos: Repository[] = action.payload!; - yield call(loadRepoListStatus, repos); - } -} - -function isInProgress(progress: number): boolean { - return progress < WorkerReservedProgress.COMPLETED && progress >= WorkerReservedProgress.INIT; -} - -// Try to trigger the repository status polling based on its current state. -function* triggerPollRepoStatus(state: RepoState, repoUri: RepositoryUri) { - switch (state) { - case RepoState.CLONING: - yield put(pollRepoCloneStatusStart(repoUri)); - break; - case RepoState.INDEXING: - yield put(pollRepoIndexStatusStart(repoUri)); - break; - case RepoState.DELETING: - yield put(pollRepoDeleteStatusStart(repoUri)); - break; - default: - break; - } -} - -function* handleReposStatusLoaded(action: Action) { - const route = yield select(routeSelector); - const allStatuses = yield select(allStatusSelector); - if (route.path === ROUTES.ADMIN) { - // Load all repository status on admin page - for (const repoUri of Object.keys(allStatuses)) { - const status = allStatuses[repoUri]; - yield triggerPollRepoStatus(status.state, repoUri); - } - } else if (route.path === ROUTES.MAIN || route.path === ROUTES.MAIN_ROOT) { - // Load current repository status on main page - const currentUri = yield select(repoUriSelector); - const status = allStatuses[currentUri]; - if (status) { - yield triggerPollRepoStatus(status.state, currentUri); - } - } -} - -export function* watchLoadRepoListStatus() { - // After all the repositories have been loaded, we should start load - // their status. - yield takeEvery(String(fetchReposSuccess), handleRepoListStatus); -} - -export function* watchLoadRepoStatus() { - // `loadRepoSuccess` is issued by the main source view page. - yield takeLatest(String(loadRepoSuccess), handleRepoStatus); -} - -export function* watchPollingRepoStatus() { - // After the status of the repos or a given repo has been loaded, check - // if we need to start polling the status. - yield takeEvery(String(loadStatusSuccess), handleReposStatusLoaded); -} - -function* handleResetPollingStatus(action: Action) { - const statuses = yield select(allStatusSelector); - for (const repoUri of Object.keys(statuses)) { - yield put(pollRepoCloneStatusStop(repoUri)); - yield put(pollRepoIndexStatusStop(repoUri)); - yield put(pollRepoDeleteStatusStop(repoUri)); - } -} - -export function* watchResetPollingStatus() { - // Stop all the repository status polling runners when route changes. - yield takeEvery(routeChange, handleResetPollingStatus); -} - -const parseCloneStatusPollingRequest = (action: Action) => { - if (action.type === String(importRepoSuccess)) { - return action.payload.uri; - } else if (action.type === String(pollRepoCloneStatusStart)) { - return action.payload; - } -}; - -const handleRepoCloneStatusProcess = function*(status: any, repoUri: RepositoryUri) { - if ( - // Repository has been deleted during the clone - (!status.gitStatus && !status.indexStatus && !status.deleteStatus) || - // Repository is in delete during the clone - status.deleteStatus - ) { - // Stop polling git progress - return false; - } - - if (status.gitStatus) { - const { progress, cloneProgress, errorMessage, timestamp } = status.gitStatus; - yield put( - updateCloneProgress({ - progress, - timestamp: moment(timestamp).toDate(), - uri: repoUri, - errorMessage, - cloneProgress, - }) - ); - // Keep polling if the progress is not 100% yet. - return isInProgress(progress); - } else { - // Keep polling if the indexStatus has not been persisted yet. - return true; - } -}; - -export function* watchRepoCloneStatusPolling() { - // The repository clone status polling will be triggered by: - // * user click import repository - // * repository status has been loaded and it's in cloning - yield takeEvery( - [String(importRepoSuccess), String(pollRepoCloneStatusStart)], - pollRepoCloneStatusRunner - ); -} - -const parseIndexStatusPollingRequest = (action: Action) => { - if (action.type === String(indexRepo) || action.type === String(pollRepoIndexStatusStart)) { - return action.payload; - } else if (action.type === String(updateCloneProgress)) { - return action.payload.uri; - } -}; - -const handleRepoIndexStatusProcess = function*(status: any, repoUri: RepositoryUri) { - if ( - // Repository has been deleted during the index - (!status.gitStatus && !status.indexStatus && !status.deleteStatus) || - // Repository is in delete during the index - status.deleteStatus - ) { - // Stop polling index progress - return false; - } - - if (status.indexStatus) { - yield put( - updateIndexProgress({ - progress: status.indexStatus.progress, - timestamp: moment(status.indexStatus.timestamp).toDate(), - uri: repoUri, - }) - ); - // Keep polling if the progress is not 100% yet. - return isInProgress(status.indexStatus.progress); - } else { - // Keep polling if the indexStatus has not been persisted yet. - return true; - } -}; - -export function* watchRepoIndexStatusPolling() { - // The repository index status polling will be triggered by: - // * user click index repository - // * clone is done - // * repository status has been loaded and it's in indexing - yield takeEvery( - [String(indexRepo), cloneCompletedPattern, String(pollRepoIndexStatusStart)], - pollRepoIndexStatusRunner - ); -} - -const parseDeleteStatusPollingRequest = (action: Action) => { - return action.payload; -}; - -const handleRepoDeleteStatusProcess = function*(status: any, repoUri: RepositoryUri) { - if (!status.gitStatus && !status.indexStatus && !status.deleteStatus) { - // If all the statuses cannot be found, this indicates the the repository has been successfully - // removed. - yield put( - updateDeleteProgress({ - progress: WorkerReservedProgress.COMPLETED, - uri: repoUri, - }) - ); - return false; - } - - if (status.deleteStatus) { - yield put( - updateDeleteProgress({ - progress: status.deleteStatus.progress, - timestamp: moment(status.deleteStatus.timestamp).toDate(), - uri: repoUri, - }) - ); - return isInProgress(status.deleteStatus.progress); - } else { - // Keep polling if the deleteStatus has not been persisted yet. - return true; - } -}; - -export function* watchRepoDeleteStatusPolling() { - // The repository delete status polling will be triggered by: - // * user click delete repository - // * repository status has been loaded and it's in deleting - yield takeEvery( - [String(deleteRepo), String(pollRepoDeleteStatusStart)], - pollRepoDeleteStatusRunner - ); -} - -function createRepoStatusPollingRun(handleStatus: any, pollingStopActionFunction: any) { - return function*(repoUri: RepositoryUri) { - try { - while (true) { - // Delay at the beginning to allow some time for the server to consume the - // queue task. - yield call(delay, REPO_STATUS_POLLING_FREQ_MS); - - const repoStatus = yield call(fetchStatus, repoUri); - const keepPolling = yield handleStatus(repoStatus, repoUri); - if (!keepPolling) { - yield put(pollingStopActionFunction(repoUri)); - } - } - } finally { - if (yield cancelled()) { - // Do nothing here now. - } - } - }; -} - -function createRepoStatusPollingRunner( - parseRepoUri: (_: Action) => RepositoryUri, - pollStatusRun: any, - pollingStopActionFunction: any, - pollingStopActionFunctionPattern: any -) { - return function*(action: Action) { - const repoUri = parseRepoUri(action); - - // Cancel existing runner to deduplicate the polling - yield put(pollingStopActionFunction(repoUri)); - - // Make a fork to run the repo index status polling - const task = yield fork(pollStatusRun, repoUri); - - // Wait for the cancellation task - yield take(pollingStopActionFunctionPattern(repoUri)); - - // Cancel the task - yield cancel(task); - }; -} - -const runPollRepoCloneStatus = createRepoStatusPollingRun( - handleRepoCloneStatusProcess, - pollRepoCloneStatusStop -); - -const runPollRepoIndexStatus = createRepoStatusPollingRun( - handleRepoIndexStatusProcess, - pollRepoIndexStatusStop -); - -const runPollRepoDeleteStatus = createRepoStatusPollingRun( - handleRepoDeleteStatusProcess, - pollRepoDeleteStatusStop -); - -const pollRepoCloneStatusRunner = createRepoStatusPollingRunner( - parseCloneStatusPollingRequest, - runPollRepoCloneStatus, - pollRepoCloneStatusStop, - cloneRepoStatusPollingStopPattern -); - -const pollRepoIndexStatusRunner = createRepoStatusPollingRunner( - parseIndexStatusPollingRequest, - runPollRepoIndexStatus, - pollRepoIndexStatusStop, - indexRepoStatusPollingStopPattern -); - -const pollRepoDeleteStatusRunner = createRepoStatusPollingRunner( - parseDeleteStatusPollingRequest, - runPollRepoDeleteStatus, - pollRepoDeleteStatusStop, - deleteRepoStatusPollingStopPattern -); diff --git a/x-pack/legacy/plugins/code/public/sagas/repository.ts b/x-pack/legacy/plugins/code/public/sagas/repository.ts deleted file mode 100644 index c8f5ce9a67557..0000000000000 --- a/x-pack/legacy/plugins/code/public/sagas/repository.ts +++ /dev/null @@ -1,182 +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 { npStart } from 'ui/new_platform'; - -import { Action } from 'redux-actions'; -import { call, put, takeEvery, takeLatest, take } from 'redux-saga/effects'; -import { - deleteRepo, - deleteRepoFailed, - deleteRepoSuccess, - fetchRepoConfigFailed, - fetchRepoConfigs, - fetchRepoConfigSuccess, - fetchRepos, - fetchReposFailed, - fetchReposSuccess, - gotoRepo, - importRepo, - importRepoFailed, - importRepoSuccess, - indexRepo, - indexRepoFailed, - indexRepoSuccess, - initRepoCommand, - updateDeleteProgress, - updateIndexProgress, - gotoRepoFailed, - loadRepo, - loadRepoSuccess, - loadRepoFailed, -} from '../actions'; -import { loadLanguageServers } from '../actions/language_server'; -import { history } from '../utils/url'; -import { adminRoutePattern } from './patterns'; - -function requestRepos(): any { - return npStart.core.http.get('/api/code/repos'); -} - -function* handleFetchRepos() { - try { - const repos = yield call(requestRepos); - yield put(fetchReposSuccess(repos)); - } catch (err) { - yield put(fetchReposFailed(err)); - } -} - -function requestDeleteRepo(uri: string) { - return npStart.core.http.delete(`/api/code/repo/${uri}`); -} - -function requestIndexRepo(uri: string) { - return npStart.core.http.post(`/api/code/repo/index/${uri}`, { - body: JSON.stringify({ reindex: true }), - }); -} - -function* handleDeleteRepo(action: Action) { - try { - yield call(requestDeleteRepo, action.payload || ''); - yield put(deleteRepoSuccess(action.payload || '')); - yield put( - updateDeleteProgress({ - uri: action.payload!, - progress: 0, - }) - ); - } catch (err) { - yield put(deleteRepoFailed(err)); - } -} - -function* handleIndexRepo(action: Action) { - try { - yield call(requestIndexRepo, action.payload || ''); - yield put(indexRepoSuccess(action.payload || '')); - yield put( - updateIndexProgress({ - uri: action.payload!, - progress: 0, - }) - ); - } catch (err) { - yield put(indexRepoFailed(err)); - } -} - -function requestImportRepo(uri: string) { - return npStart.core.http.post('/api/code/repo', { - body: JSON.stringify({ url: uri }), - }); -} - -function* handleImportRepo(action: Action) { - try { - const data = yield call(requestImportRepo, action.payload || ''); - yield put(importRepoSuccess(data)); - } catch (err) { - yield put(importRepoFailed(err)); - } -} -function* handleFetchRepoConfigs() { - try { - const configs = yield call(requestRepoConfigs); - yield put(fetchRepoConfigSuccess(configs)); - } catch (e) { - yield put(fetchRepoConfigFailed(e)); - } -} - -function requestRepoConfigs() { - return npStart.core.http.get('/api/code/workspace'); -} - -function* handleInitCmd(action: Action) { - const repoUri = action.payload as string; - yield call(requestRepoInitCmd, repoUri); -} - -function requestRepoInitCmd(repoUri: string) { - return npStart.core.http.post(`/api/code/workspace/${repoUri}/master`, { - query: { force: true }, - }); -} -function* handleGotoRepo(action: Action) { - try { - const repoUri = action.payload as string; - yield put(loadRepo(repoUri)); - const loadRepoDoneAction = yield take([String(loadRepoSuccess), String(loadRepoFailed)]); - if (loadRepoDoneAction.type === String(loadRepoSuccess)) { - history.replace(`/${repoUri}/tree/${loadRepoDoneAction.payload.defaultBranch || 'master'}`); - } else { - // redirect to root project path if repo not found to show 404 page - history.replace(`/${action.payload}/tree/master`); - } - } catch (e) { - history.replace(`/${action.payload}/tree/master`); - yield put(gotoRepoFailed(e)); - } -} - -export function* watchImportRepo() { - yield takeEvery(String(importRepo), handleImportRepo); -} - -export function* watchDeleteRepo() { - yield takeEvery(String(deleteRepo), handleDeleteRepo); -} - -export function* watchIndexRepo() { - yield takeEvery(String(indexRepo), handleIndexRepo); -} - -export function* watchFetchRepos() { - yield takeEvery(String(fetchRepos), handleFetchRepos); -} - -export function* watchFetchRepoConfigs() { - yield takeEvery(String(fetchRepoConfigs), handleFetchRepoConfigs); -} - -export function* watchInitRepoCmd() { - yield takeEvery(String(initRepoCommand), handleInitCmd); -} - -export function* watchGotoRepo() { - yield takeLatest(String(gotoRepo), handleGotoRepo); -} - -function* handleAdminRouteChange() { - yield put(fetchRepos()); - yield put(fetchRepoConfigs()); - yield put(loadLanguageServers()); -} - -export function* watchAdminRouteChange() { - yield takeLatest(adminRoutePattern, handleAdminRouteChange); -} diff --git a/x-pack/legacy/plugins/code/public/sagas/route.ts b/x-pack/legacy/plugins/code/public/sagas/route.ts deleted file mode 100644 index bafb0f5326f31..0000000000000 --- a/x-pack/legacy/plugins/code/public/sagas/route.ts +++ /dev/null @@ -1,80 +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 { put, takeEvery, select, takeLatest } from 'redux-saga/effects'; -import { Action } from 'redux-actions'; -import { - routeChange, - Match, - loadRepo, - fetchRepoBranches, - fetchRepoCommits, - fetchRootRepoTree, -} from '../actions'; -import { previousMatchSelector, repoUriSelector, revisionSelector } from '../selectors'; -import { routePathChange, repoChange, revisionChange, filePathChange } from '../actions/route'; -import * as ROUTES from '../components/routes'; - -const MAIN_ROUTES = [ROUTES.MAIN, ROUTES.MAIN_ROOT]; - -function* handleRepoOrRevisionChange() { - const repoUri = yield select(repoUriSelector); - const revision = yield select(revisionSelector); - yield put(fetchRepoCommits({ uri: repoUri, revision })); - yield put(fetchRootRepoTree({ uri: repoUri, revision })); -} - -export function* watchRepoOrRevisionChange() { - yield takeLatest([String(repoChange), String(revisionChange)], handleRepoOrRevisionChange); -} - -const getRepoFromMatch = (match: Match) => - `${match.params.resource}/${match.params.org}/${match.params.repo}`; - -function* handleRoute(action: Action) { - const currentMatch = action.payload; - const previousMatch = yield select(previousMatchSelector); - if (MAIN_ROUTES.includes(currentMatch.path)) { - if (MAIN_ROUTES.includes(previousMatch.path)) { - const currentRepo = getRepoFromMatch(currentMatch); - const previousRepo = getRepoFromMatch(previousMatch); - const currentRevision = currentMatch.params.revision; - const previousRevision = previousMatch.params.revision; - const currentFilePath = currentMatch.params.path; - const previousFilePath = previousMatch.params.path; - if (currentRepo !== previousRepo) { - yield put(repoChange(currentRepo)); - } - if (currentRevision !== previousRevision) { - yield put(revisionChange()); - } - if (currentFilePath !== previousFilePath) { - yield put(filePathChange()); - } - } else { - yield put(routePathChange()); - const currentRepo = getRepoFromMatch(currentMatch); - yield put(repoChange(currentRepo)); - yield put(revisionChange()); - yield put(filePathChange()); - } - } else if (currentMatch.path !== previousMatch.path) { - yield put(routePathChange()); - } -} - -export function* watchRoute() { - yield takeEvery(String(routeChange), handleRoute); -} - -export function* handleRepoChange(action: Action) { - yield put(loadRepo(action.payload!)); - yield put(fetchRepoBranches({ uri: action.payload! })); -} - -export function* watchRepoChange() { - yield takeEvery(String(repoChange), handleRepoChange); -} diff --git a/x-pack/legacy/plugins/code/public/sagas/search.ts b/x-pack/legacy/plugins/code/public/sagas/search.ts deleted file mode 100644 index a9e8205d3b764..0000000000000 --- a/x-pack/legacy/plugins/code/public/sagas/search.ts +++ /dev/null @@ -1,144 +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 queryString from 'querystring'; -import { npStart } from 'ui/new_platform'; - -import { Action } from 'redux-actions'; -import { call, put, takeEvery, takeLatest } from 'redux-saga/effects'; - -import { SearchScope } from '../../model'; -import { - changeSearchScope, - documentSearch, - documentSearchFailed, - DocumentSearchPayload, - documentSearchSuccess, - Match, - repositorySearch, - repositorySearchFailed, - RepositorySearchPayload, - repositorySearchQueryChanged, - repositorySearchSuccess, - searchReposForScope, - searchReposForScopeFailed, - searchReposForScopeSuccess, - turnOffDefaultRepoScope, -} from '../actions'; -import { adminRoutePattern, searchRoutePattern } from './patterns'; - -function requestDocumentSearch(payload: DocumentSearchPayload) { - const { query, page, languages, repositories, repoScope } = payload; - const queryParams: { [key: string]: string | number | boolean } = { - q: query, - }; - - if (page) { - queryParams.p = page; - } - - if (languages) { - queryParams.langs = languages; - } - - if (repositories) { - queryParams.repos = repositories; - } - - if (repoScope) { - queryParams.repoScope = repoScope; - } - - if (query && query.length > 0) { - return npStart.core.http.get(`/api/code/search/doc`, { - query: queryParams, - }); - } else { - return { - documents: [], - took: 0, - total: 0, - }; - } -} - -function* handleDocumentSearch(action: Action) { - try { - const data = yield call(requestDocumentSearch, action.payload!); - yield put(documentSearchSuccess(data)); - } catch (err) { - yield put(documentSearchFailed(err)); - } -} - -function requestRepositorySearch(q: string) { - return npStart.core.http.get(`/api/code/search/repo`, { - query: { q }, - }); -} - -export function* watchDocumentSearch() { - yield takeLatest(String(documentSearch), handleDocumentSearch); -} - -function* handleRepositorySearch(action: Action) { - try { - const data = yield call(requestRepositorySearch, action.payload!.query); - yield put(repositorySearchSuccess(data)); - } catch (err) { - yield put(repositorySearchFailed(err)); - } -} - -export function* watchRepositorySearch() { - yield takeLatest( - [String(repositorySearch), String(repositorySearchQueryChanged)], - handleRepositorySearch - ); -} - -function* handleSearchRouteChange(action: Action) { - const { location } = action.payload!; - const rawSearchStr = location.search.length > 0 ? location.search.substring(1) : ''; - const queryParams = queryString.parse(rawSearchStr); - const { q, p, langs, repos, scope, repoScope } = queryParams; - yield put(changeSearchScope(scope as SearchScope)); - if (scope === SearchScope.REPOSITORY) { - yield put(repositorySearch({ query: q as string })); - } else { - yield put( - documentSearch({ - query: q as string, - page: p as string, - languages: langs as string, - repositories: repos as string, - repoScope: repoScope as string, - }) - ); - } -} - -function* resetDefaultRepoScope() { - yield put(turnOffDefaultRepoScope()); -} - -export function* watchSearchRouteChange() { - yield takeLatest(searchRoutePattern, handleSearchRouteChange); - // Reset the default search scope if enters the admin page. - yield takeLatest(adminRoutePattern, resetDefaultRepoScope); -} - -function* handleReposSearchForScope(action: Action) { - try { - const data = yield call(requestRepositorySearch, action.payload!.query); - yield put(searchReposForScopeSuccess(data)); - } catch (err) { - yield put(searchReposForScopeFailed(err)); - } -} - -export function* watchRepoScopeSearch() { - yield takeEvery(searchReposForScope, handleReposSearchForScope); -} diff --git a/x-pack/legacy/plugins/code/public/sagas/setup.ts b/x-pack/legacy/plugins/code/public/sagas/setup.ts deleted file mode 100644 index 7c7910b695d98..0000000000000 --- a/x-pack/legacy/plugins/code/public/sagas/setup.ts +++ /dev/null @@ -1,28 +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 { npStart } from 'ui/new_platform'; -import { call, put, takeEvery } from 'redux-saga/effects'; -import { checkSetupFailed, checkSetupSuccess } from '../actions'; -import { rootRoutePattern, setupRoutePattern } from './patterns'; - -function* handleRootRoute() { - try { - yield call(requestSetup); - yield put(checkSetupSuccess()); - } catch (e) { - yield put(checkSetupFailed()); - } -} - -function requestSetup() { - return npStart.core.http.head(`/api/code/setup`); -} - -export function* watchRootRoute() { - yield takeEvery(rootRoutePattern, handleRootRoute); - yield takeEvery(setupRoutePattern, handleRootRoute); -} diff --git a/x-pack/legacy/plugins/code/public/sagas/status.ts b/x-pack/legacy/plugins/code/public/sagas/status.ts deleted file mode 100644 index 1a1507153ba05..0000000000000 --- a/x-pack/legacy/plugins/code/public/sagas/status.ts +++ /dev/null @@ -1,153 +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 { Action } from 'redux-actions'; -import { call, put, select, takeEvery, takeLatest } from 'redux-saga/effects'; -import { npStart } from 'ui/new_platform'; -import { isEqual } from 'lodash'; -import { delay } from 'redux-saga'; - -import { RepositoryUri, WorkerReservedProgress } from '../../model'; -import { - deleteRepoFinished, - FetchFilePayload, - FetchRepoFileStatus, - FetchRepoFileStatusFailed, - FetchRepoFileStatusSuccess, - Match, - pollRepoCloneStatusStop, - pollRepoDeleteStatusStop, - pollRepoIndexStatusStop, - routeChange, - StatusChanged, - updateCloneProgress, - updateDeleteProgress, -} from '../actions'; -import * as ROUTES from '../components/routes'; -import { RootState } from '../reducers'; -import { adminRoutePattern, mainRoutePattern } from './patterns'; -import { RepoFileStatus, StatusReport } from '../../common/repo_file_status'; -import { statusSelector } from '../selectors'; - -const matchSelector = (state: RootState) => state.route.match; - -export const cloneCompletedPattern = (action: Action) => - action.type === String(updateCloneProgress) && - action.payload.progress === WorkerReservedProgress.COMPLETED; - -const deleteCompletedPattern = (action: Action) => - action.type === String(updateDeleteProgress) && - action.payload.progress === WorkerReservedProgress.COMPLETED; - -export const cloneRepoStatusPollingStopPattern = (repoUri: RepositoryUri) => { - return (action: Action) => { - return action.type === String(pollRepoCloneStatusStop) && action.payload === repoUri; - }; -}; - -export const indexRepoStatusPollingStopPattern = (repoUri: RepositoryUri) => { - return (action: Action) => { - return action.type === String(pollRepoIndexStatusStop) && action.payload === repoUri; - }; -}; - -export const deleteRepoStatusPollingStopPattern = (repoUri: RepositoryUri) => { - return (action: Action) => { - return action.type === String(pollRepoDeleteStatusStop) && action.payload === repoUri; - }; -}; - -function* handleRepoCloneSuccess() { - const match: Match = yield select(matchSelector); - if (match.path === ROUTES.MAIN || match.path === ROUTES.MAIN_ROOT) { - yield put(routeChange(match)); - } -} - -export function* watchRepoCloneSuccess() { - yield takeEvery(cloneCompletedPattern, handleRepoCloneSuccess); -} - -function* handleRepoDeleteFinished(action: any) { - yield put(deleteRepoFinished(action.payload.uri)); -} - -export function* watchRepoDeleteFinished() { - yield takeEvery(deleteCompletedPattern, handleRepoDeleteFinished); -} - -function* handleMainRouteChange(action: Action) { - // in source view page, we need repos as default repo scope options when no query input - const { resource, org, repo, path, revision } = action.payload!.params; - const uri = `${resource}/${org}/${repo}`; - const newStatusPath: FetchFilePayload = { uri, revision, path }; - const currentStatusPath = yield select((state: RootState) => state.status.currentStatusPath); - if (!isEqual(newStatusPath, currentStatusPath)) { - yield call(startPollingStatus, newStatusPath); - } -} -const STATUS_POLLING_FREQ_HIGH_MS = 3000; -const STATUS_POLLING_FREQ_LOW_MS = 10000; - -let polling: boolean = false; - -function stopPollingStatus() { - polling = false; -} -function* startPollingStatus(location: FetchFilePayload) { - yield put(FetchRepoFileStatus(location)); - polling = true; - let currentStatusPath = yield select((state: RootState) => state.status.currentStatusPath); - while (polling && isEqual(location, currentStatusPath)) { - const previousStatus: StatusReport = yield select(statusSelector); - const newStatus: StatusReport = yield call(fetchStatus, location); - let delayMs = STATUS_POLLING_FREQ_LOW_MS; - if (newStatus) { - yield call(compareStatus, previousStatus, newStatus); - if (newStatus.langServerStatus === RepoFileStatus.LANG_SERVER_IS_INITIALIZING) { - delayMs = STATUS_POLLING_FREQ_HIGH_MS; - } - currentStatusPath = yield select((state: RootState) => state.status.currentStatusPath); - } - yield call(delay, delayMs); - } -} - -function* compareStatus(prevStatus: StatusReport, currentStatus: StatusReport) { - if (!isEqual(prevStatus, currentStatus)) { - yield put(StatusChanged({ prevStatus, currentStatus })); - } -} - -function* fetchStatus(location: FetchFilePayload) { - yield put(FetchRepoFileStatus(location)); - try { - const newStatus: StatusReport = yield call(requestStatus, location); - yield put( - FetchRepoFileStatusSuccess({ - statusReport: newStatus, - path: location, - }) - ); - return newStatus; - } catch (e) { - yield put(FetchRepoFileStatusFailed(e)); - } -} - -function requestStatus(location: FetchFilePayload) { - const { uri, revision, path } = location; - const pathname = path - ? `/api/code/repo/${uri}/status/${revision}/${path}` - : `/api/code/repo/${uri}/status/${revision}`; - - return npStart.core.http.get(pathname); -} - -export function* watchStatusChange() { - yield takeLatest(mainRoutePattern, handleMainRouteChange); - yield takeLatest(adminRoutePattern, stopPollingStatus); -} diff --git a/x-pack/legacy/plugins/code/public/sagas/structure.ts b/x-pack/legacy/plugins/code/public/sagas/structure.ts deleted file mode 100644 index 010d6776530e0..0000000000000 --- a/x-pack/legacy/plugins/code/public/sagas/structure.ts +++ /dev/null @@ -1,96 +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 { Action } from 'redux-actions'; -import { call, put, select, takeEvery } from 'redux-saga/effects'; -import { DocumentSymbol } from 'vscode-languageserver-types'; -import { LspRestClient, TextDocumentMethods } from '../../common/lsp_client'; -import { - loadStructure, - loadStructureFailed, - loadStructureSuccess, - StatusChanged, -} from '../actions'; -import { SymbolWithMembers } from '../actions/structure'; -import { RepoFileStatus, StatusReport } from '../../common/repo_file_status'; -import { RootState } from '../reducers'; -import { toCanonicalUrl } from '../../common/uri_util'; - -const sortSymbol = (a: SymbolWithMembers, b: SymbolWithMembers) => { - const lineDiff = a.range.start.line - b.range.start.line; - if (lineDiff === 0) { - return a.range.start.character - b.range.start.character; - } else { - return lineDiff; - } -}; - -const generateStructureTree: (documentSymbol: DocumentSymbol, path: string) => SymbolWithMembers = ( - documentSymbol, - path -) => { - const currentPath = path ? `${path}/${documentSymbol.name}` : documentSymbol.name; - const structureTree: SymbolWithMembers = { - name: documentSymbol.name, - kind: documentSymbol.kind, - path: currentPath, - range: documentSymbol.range, - selectionRange: documentSymbol.selectionRange, - }; - if (documentSymbol.children) { - structureTree.members = documentSymbol.children - .sort(sortSymbol) - .map(ds => generateStructureTree(ds, currentPath)); - } - return structureTree; -}; - -function requestStructure(uri?: string) { - const lspClient = new LspRestClient('/api/code/lsp'); - const lspMethods = new TextDocumentMethods(lspClient); - return lspMethods.documentSymbol.send({ - textDocument: { - uri: uri || '', - }, - }); -} - -function* statusChanged(action: Action) { - const { - prevStatus, - currentStatus, - }: { prevStatus: StatusReport; currentStatus: StatusReport } = action.payload; - if ( - prevStatus && - prevStatus.langServerStatus === RepoFileStatus.LANG_SERVER_IS_INITIALIZING && - currentStatus.langServerStatus !== RepoFileStatus.LANG_SERVER_IS_INITIALIZING - ) { - const { revision, uri, path } = yield select( - (state: RootState) => state.status.currentStatusPath - ); - const u = toCanonicalUrl({ - repoUri: uri, - file: path, - revision, - }); - yield call(fetchSymbols, loadStructure(u)); - } -} - -export function* watchLoadStructure() { - yield takeEvery(String(loadStructure), fetchSymbols); - yield takeEvery(String(StatusChanged), statusChanged); -} - -function* fetchSymbols(action: Action) { - try { - const data: DocumentSymbol[] = yield call(requestStructure, `git:/${action.payload}`); - const structureTree = data.sort(sortSymbol).map(ds => generateStructureTree(ds, '')); - yield put(loadStructureSuccess({ path: action.payload!, data, structureTree })); - } catch (e) { - yield put(loadStructureFailed(e)); - } -} diff --git a/x-pack/legacy/plugins/code/public/selectors/index.ts b/x-pack/legacy/plugins/code/public/selectors/index.ts deleted file mode 100644 index 870cf1cee3e81..0000000000000 --- a/x-pack/legacy/plugins/code/public/selectors/index.ts +++ /dev/null @@ -1,107 +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 { FileTree, RepositoryUri } from '../../model'; -import { RootState } from '../reducers'; - -export const getTree = (state: RootState) => state.fileTree.tree; -export const getTreeRevision = (state: RootState) => state.fileTree.revision; - -export const lastRequestPathSelector: (state: RootState) => string = (state: RootState) => - state.symbol.lastRequestPath || ''; - -export const structureSelector = (state: RootState) => { - const pathname = lastRequestPathSelector(state); - const symbols = state.symbol.structureTree[pathname]; - return symbols || []; -}; - -export const refUrlSelector = (state: RootState) => { - const payload = state.editor.refPayload; - if (payload) { - const { line, character } = payload.position; - return `${payload.textDocument.uri}!L${line}:${character}`; - } - return undefined; -}; - -export const fileSelector = (state: RootState) => state.file.file; - -export const searchScopeSelector = (state: RootState) => state.search.scope; - -export const repoUriSelector = (state: RootState) => { - const { resource, org, repo } = state.route.match.params; - return `${resource}/${org}/${repo}`; -}; -export const revisionSelector = (state: RootState) => state.route.match.params.revision; - -export const routeSelector = (state: RootState) => state.route.match; - -export const repoStatusSelector = (state: RootState, repoUri: RepositoryUri) => { - return state.status.status[repoUri]; -}; - -export const allStatusSelector = (state: RootState) => state.status.status; - -export const currentPathSelector = (state: RootState) => state.route.match.params.path || ''; - -export const treeCommitsSelector = (state: RootState) => { - const path = currentPathSelector(state); - return state.revision.treeCommits[path] || []; -}; - -export const hasMoreCommitsSelector = (state: RootState) => { - const path = currentPathSelector(state); - const isLoading = state.revision.loadingCommits; - if (isLoading) { - return false; - } - if (state.revision.commitsFullyLoaded[path]) { - return false; - } - const commits = state.revision.treeCommits[path]; - if (!commits) { - // To avoid infinite loops in component `InfiniteScroll`, - // here we set hasMore to false before we receive the first batch. - return false; - } - return true; -}; - -function find(tree: FileTree, paths: string[]): FileTree | null { - if (paths.length === 0) { - return tree; - } - const [p, ...rest] = paths; - if (tree.children) { - const child = tree.children.find((c: FileTree) => c.name === p); - if (child) { - return find(child, rest); - } - } - return null; -} - -export const currentTreeSelector = (state: RootState) => { - const tree = getTree(state); - const path = currentPathSelector(state) || ''; - return find(tree, path.split('/')); -}; - -export const createTreeSelector = (path: string) => (state: RootState) => { - const tree = getTree(state); - return find(tree, path.split('/')); -}; - -export const currentRepoSelector = (state: RootState) => state.repository.repository; - -export const repoScopeSelector = (state: RootState) => state.search.searchOptions.repoScope; - -export const urlQueryStringSelector = (state: RootState) => state.route.match.location.search; - -export const previousMatchSelector = (state: RootState) => state.route.previousMatch; - -export const statusSelector = (state: RootState) => state.status.repoFileStatus; -export const reposSelector = (state: RootState) => state.repositoryManagement.repositories; diff --git a/x-pack/legacy/plugins/code/public/services/ui_metric.ts b/x-pack/legacy/plugins/code/public/services/ui_metric.ts deleted file mode 100644 index 245ca14e77aa7..0000000000000 --- a/x-pack/legacy/plugins/code/public/services/ui_metric.ts +++ /dev/null @@ -1,13 +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 { - createUiStatsReporter, - METRIC_TYPE, -} from '../../../../../../src/legacy/core_plugins/ui_metric/public'; -import { APP_USAGE_TYPE } from '../../common/constants'; - -export const trackCodeUiMetric = createUiStatsReporter(APP_USAGE_TYPE); -export { METRIC_TYPE }; diff --git a/x-pack/legacy/plugins/code/public/shared.ts b/x-pack/legacy/plugins/code/public/shared.ts deleted file mode 100644 index 8147ed8d43987..0000000000000 --- a/x-pack/legacy/plugins/code/public/shared.ts +++ /dev/null @@ -1,11 +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 * from './components/code_block'; -export { - CodeIntegrator, - Props as CodeIntegratorProps, -} from './components/integrations/code_integrator'; diff --git a/x-pack/legacy/plugins/code/public/stores/index.ts b/x-pack/legacy/plugins/code/public/stores/index.ts deleted file mode 100644 index b502ba97aa676..0000000000000 --- a/x-pack/legacy/plugins/code/public/stores/index.ts +++ /dev/null @@ -1,18 +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 { applyMiddleware, compose, createStore } from 'redux'; -import createSagaMiddleware from 'redux-saga'; - -import { rootReducer } from '../reducers'; -import { rootSaga } from '../sagas'; - -const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; - -const sagaMW = createSagaMiddleware(); - -export const store = createStore(rootReducer, composeEnhancers(applyMiddleware(sagaMW))); - -sagaMW.run(rootSaga); diff --git a/x-pack/legacy/plugins/code/public/style/_buttons.scss b/x-pack/legacy/plugins/code/public/style/_buttons.scss deleted file mode 100644 index 1f9dc1ff84e73..0000000000000 --- a/x-pack/legacy/plugins/code/public/style/_buttons.scss +++ /dev/null @@ -1,27 +0,0 @@ -.codeButton__project { - border-radius: $euiBorderRadius; - height: 4rem; - padding: $euiSizeS; - width: 4rem; - display: flex; - flex-direction: column; - align-items: center; - justify-content: space-between; - cursor: pointer; - transition: background-color $euiAnimSpeedFast $euiAnimSlightResistance; - padding: $euiSizeM 0; - &:hover { - background-color: $euiColorLightestShade; - } -} - -.codeButton__projectImport { - margin-top: $euiSizeL; -} - -.codeButtonGroup { - margin-left: $euiSizeS; - .euiButton { - font-size: $euiFontSizeS; - } -} diff --git a/x-pack/legacy/plugins/code/public/style/_commits.scss b/x-pack/legacy/plugins/code/public/style/_commits.scss deleted file mode 100644 index edf212fe03224..0000000000000 --- a/x-pack/legacy/plugins/code/public/style/_commits.scss +++ /dev/null @@ -1,22 +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. - */ - -.commit__actions { - flex-shrink: 0; - margin-left: $euiSizeXL; -} - -.commit__metadata { - margin-bottom: $euiSizeXS; -} - -.commit__inline-separator { - margin: 0 $euiSizeS; -} - -.commit__diff-link { - @include euiCodeFont; -} diff --git a/x-pack/legacy/plugins/code/public/style/_filetree.scss b/x-pack/legacy/plugins/code/public/style/_filetree.scss deleted file mode 100644 index fb476e39cc3bb..0000000000000 --- a/x-pack/legacy/plugins/code/public/style/_filetree.scss +++ /dev/null @@ -1,66 +0,0 @@ -%extendCodeNode__item { - padding: $euiSizeXS $euiSizeXS $euiSizeXS $euiSize; - cursor: pointer; - white-space: nowrap; - margin-left: $euiSizeS; - z-index: 2; -} - -.codeFileTree__container .euiSideNavItem--branch { - padding-left: $euiSize; -} - -.codeFileTree__node { - display: flex; - flex-direction: row; -} - -.codeFileTree__item { - @extend %extendCodeNode__item; -} - -.codeFileTree__file { - margin-left: calc(28rem / 16); -} - -.codeFileTree__directory { - margin-left: $euiSizeS; - vertical-align: middle; -} - -.codeFileTree__node--link { - position: absolute; - width: 100%; - left: 0; - height: 1.665rem; - opacity: 0; - z-index: $euiZLevel1; -} - -.codeFileTree__node--fullWidth { - cursor: pointer; - position: absolute; - width: 100%; - background: $euiColorLightShade; - left: 0; - height: 1.665rem; - // Use box shadows instead of tricky absolute positioning to set the active state - box-shadow: -10rem 0 0 $euiColorLightShade, 10rem 0 0 $euiColorLightShade; -} - -.codeFileTree__container { - .euiSideNavItem__items { - margin: 0; - } -} - -@include euiBreakpoint('xs', 's') { - .codeFileTree__container { - margin-left: -$euiSizeXL; - margin-top: -$euiSizeL; - - .euiSideNav__mobileToggle { - display: none; - } - } -} diff --git a/x-pack/legacy/plugins/code/public/style/_filters.scss b/x-pack/legacy/plugins/code/public/style/_filters.scss deleted file mode 100644 index 5519efa6a722e..0000000000000 --- a/x-pack/legacy/plugins/code/public/style/_filters.scss +++ /dev/null @@ -1,15 +0,0 @@ -.codeFilter__groups { - padding: $euiSize; -} - -.codeFilter__group { - margin-bottom: $euiSizeS; -} - -.codeFilter__group-icon { - width: $codeSideBarIndent; -} - -.codeFilter__item { - margin-left: $codeSideBarIndent; -} diff --git a/x-pack/legacy/plugins/code/public/style/_layout.scss b/x-pack/legacy/plugins/code/public/style/_layout.scss deleted file mode 100644 index 792cb0944cf3e..0000000000000 --- a/x-pack/legacy/plugins/code/public/style/_layout.scss +++ /dev/null @@ -1,207 +0,0 @@ -.codeContainer__adminWrapper { - width: 100%; - flex-grow: 1; - display: flex; - flex-direction: column; - height: 100%; -} - -.codeContainer__adminMain { - overflow: auto; - padding: $euiSize $euiSizeXL; -} - -.codeContainer__root { - position: absolute; - height: 100%; - width: 100%; - display: flex; - flex-direction: column; -} - -.codeContainer__rootInner { - display: flex; - flex-direction: row; - flex-grow: 1; - height: 100%; -} - -.codeContainer__main { - width: calc(100% - 16rem); - flex-grow: 1; - display: flex; - flex-direction: column; - height: 100%; -} - -.codeContainer__import { - max-width: 56rem; - margin: auto; -} - -.codeContainer__setup { - width: 56rem; - margin: 0 auto; - padding: 0 0 $euiSizeXXL 0; - - .codeContainer__setup--step { - margin-top: $euiSizeXXL; - width: 100%; - } -} - -.codeContainer__editor { - overflow: hidden; - flex-grow: 1; -} - -.codeContainer__blame { - position: relative; - overflow: hidden; - flex-grow: 1; -} - -.codeContainer__directoryView, -.codeContainer__history { - flex-grow: 1; - overflow: auto; -} - -.codeContainer__select { - margin-right: $euiSizeS; - width: 12rem; - flex-grow: 0; - flex-shrink: 0; -} - -.codeContainer__search--inner { - overflow-y: scroll; - padding: 1rem; -} - -.codeContainer__search { - position: absolute; - display: flex; - flex-direction: column; - height: 100%; - width: 100%; -} - -.codeContainer__search--main { - overflow: hidden; - width: 100%; - flex-grow: 1; - display: flex; - flex-direction: column; - height: 100%; -} - -.codeContainer__sideTabTree { - position: relative; - display: inline-block; - min-width: 100%; -} - -.codeContainer__search--results { - margin-top: $euiSize; -} - -.codeViewer { - flex-grow: 1; -} - -.codeSidebar__container { - background-color: $euiColorLightestShade; - border-right: solid 1px $euiBorderColor; - flex-grow: 1; - flex-shrink: 1; - overflow: auto; - max-width: $codeSideBarWidth; - min-width: $codeSideBarWidth; -} - -.codeSearchbar__container { - height: 3.5rem; - padding: $codeSearchBarPadding; - border-bottom: $euiBorderWidthThin solid $euiBorderColor; -} - -.codeSearchSettings__flyout { - max-width: 28rem; -} - -.codeFooter--error { - color: $euiColorDanger; -} - -.codePanel__project { - position: relative; - margin-bottom: $euiSizeS; -} - -.codePanel__project--error { - border: 2px solid $euiColorDanger; -} - -.codeSettingsPanel__icon { - display: inline-block; - position: relative; - top: $euiSizeS; -} - -.codeText__blameMessage { - max-width: 10rem; -} - -.codeAvatar { - margin: auto $euiSizeS auto 0; -} - -.codeContainer__progress { - width: 40rem; - padding: $euiSizeXS; - border: $euiBorderThin; -} - -.codeContainer__commitMessages { - overflow: auto; - flex: 1; - padding: $euiSizeM; - - .codeHeader__commit { - display: flex; - flex-direction: row; - justify-content: space-between; - } -} - -.codeContainer__directoryList { - padding: $euiSizeL; - &:first-child { - padding-bottom: 0; - } - &:not(:first-child) { - padding-top: 0; - padding-bottom: 0; - } -} - -.codePanel__error { - width: 31rem; - margin: auto; -} - -.codeLoader { - padding: 1.5rem; - text-align: center; -} - -.code-symbol-container { - white-space: nowrap; -} - -.codeBreadcrumbs { - .euiPopover__anchor { - margin-top: $euiSizeXS; - } -} diff --git a/x-pack/legacy/plugins/code/public/style/_markdown.scss b/x-pack/legacy/plugins/code/public/style/_markdown.scss deleted file mode 100644 index 01ceb042f0348..0000000000000 --- a/x-pack/legacy/plugins/code/public/style/_markdown.scss +++ /dev/null @@ -1,11 +0,0 @@ -.markdown-body { - color: $euiColorDarkestShade; - a, a:visited { - color: $euiColorPrimary; - text-decoration: underline; - } -} - -.markdown-body .highlight pre, .markdown-body pre { - background-color: $euiColorLightestShade;; -} diff --git a/x-pack/legacy/plugins/code/public/style/_monaco.scss b/x-pack/legacy/plugins/code/public/style/_monaco.scss deleted file mode 100644 index b04721251a09d..0000000000000 --- a/x-pack/legacy/plugins/code/public/style/_monaco.scss +++ /dev/null @@ -1,106 +0,0 @@ -.codeSearch__highlight { - background-color: $euiColorVis5; - color: black !important; - padding: $euiSizeXS / 2; - border-radius: $euiSizeXS / 2; - font-weight: bold; - font-style: oblique; - cursor: pointer; -} - -.codeBlock__line--highlighted { - background-color: $euiColorLightShade; -} - -.monaco-diff-editor .margin-view-overlays .line-numbers, -.monaco-editor .margin-view-overlays .line-numbers { - text-align: center; - border-right: $euiBorderThin; -} - -.monaco-diff-editor-hover, -.monaco-editor-hover { - min-width: 350px; - border: $euiBorderThin; - border-bottom: 0; - cursor: default; - position: absolute; - overflow-y: auto; - z-index: 5; - -webkit-user-select: text; - -ms-user-select: text; - -moz-user-select: text; - -o-user-select: text; - user-select: text; - box-sizing: initial; - animation: fadein 0.1s linear; - line-height: 1.5em; - background: $euiColorLightestShade; - border-radius: 4px 4px 4px 4px; - @include euiBottomShadow; -} - -.monaco-diff-editor-hover .hover-row, -.monaco-editor-hover .hover-row { - padding: 4px 5px; -} - -.monaco-diff-editor-hover .button-group, -.monaco-editor-hover .button-group { - background: linear-gradient(-180deg, $euiColorLightestShade 0%, $euiColorEmptyShade 100%); - border-radius: 0 0 4px 4px; - box-shadow: 0 -1px 0 0 $euiBorderColor; - height: 33px; -} - -.monaco-diff-editor-hover .button-group button:not(:first-child), -.monaco-editor-hover .button-group button:not(:first-child) { - border-left: 1px solid $euiBorderColor; -} - -.monaco-diff-editor-hover .button-group button, -.monaco-editor-hover .button-group button { - font-size: 13px; - font-weight: normal; - border: 0; - border-radius: 0; - flex: 1; -} - -.monaco-diff-editor .scroll-decoration, -.monaco-editor .scroll-decoration { - display: none; -} - -.text-placeholder { - width: 100%; - height: 18px; - margin: 4px 0 4px 0; -} - -.gradient { - animation-duration: 1.8s; - animation-fill-mode: forwards; - animation-iteration-count: infinite; - animation-name: placeHolderShimmer; - animation-timing-function: linear; - background: $euiColorLightestShade; - background: linear-gradient( - to right, - $euiColorLightShade 8%, - $euiColorLightestShade 38%, - $euiColorLightShade 54% - ); - background-size: 1000px 640px; - - position: relative; -} - -@keyframes placeHolderShimmer { - 0% { - background-position: -468px 0; - } - 100% { - background-position: 468px 0; - } -} diff --git a/x-pack/legacy/plugins/code/public/style/_query_bar.scss b/x-pack/legacy/plugins/code/public/style/_query_bar.scss deleted file mode 100644 index 7459a8cd41e3a..0000000000000 --- a/x-pack/legacy/plugins/code/public/style/_query_bar.scss +++ /dev/null @@ -1,18 +0,0 @@ -.codeQueryBar { - max-width: 90%; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; -} - -.codeQueryBar__input { - padding-right: 10rem; -} - -.codeQueryBar__scope { - width: calc(#{$codeSideBarWidth} - #{$codeSearchBarPadding}); -} - -.codeQueryBar__filter-badge { - margin-right: $euiSizeS; -} diff --git a/x-pack/legacy/plugins/code/public/style/_shortcuts.scss b/x-pack/legacy/plugins/code/public/style/_shortcuts.scss deleted file mode 100644 index 2cf519b7f2562..0000000000000 --- a/x-pack/legacy/plugins/code/public/style/_shortcuts.scss +++ /dev/null @@ -1,23 +0,0 @@ -.codeShortcuts__key { - background: $euiColorEmptyShade; - border: $euiBorderThin; - box-sizing: border-box; - box-shadow: 0px 2px 0px $euiColorLightestShade; - border-radius: $euiSizeXS; - width: $euiSizeL; - min-width: $euiSizeL; - height: $euiSizeL; - display: inline-block; - text-align: center; - margin: $euiSizeXS; - line-height: $euiSizeL; - text-transform: uppercase; - font-size: $euiFontSizeS; -} - -.codeShortcuts__helpText { - line-height: $euiSizeL; - font-size: $euiFontSizeS; - margin-left: $euiSizeL / 2; - color: $euiColorDarkestShade; -} diff --git a/x-pack/legacy/plugins/code/public/style/_sidebar.scss b/x-pack/legacy/plugins/code/public/style/_sidebar.scss deleted file mode 100644 index ae58df8303ca4..0000000000000 --- a/x-pack/legacy/plugins/code/public/style/_sidebar.scss +++ /dev/null @@ -1,96 +0,0 @@ -.codeSidebar { - box-shadow: inset -1px 0 0 $euiColorLightShade; - padding: $euiSizeS; - flex-basis: 18rem; - flex-grow: 0; - - .codeSidebar__heading { - margin: $euiSizeM $euiSizeM 0 $euiSizeM; - } - - .code-sidebar__link { - background-color: transparent; - border-radius: $euiBorderRadius; - box-sizing: border-box; - padding-right: $euiSizeS; - width: 100%; - transition: all $euiAnimSpeedFast $euiAnimSlightResistance; - &:hover { - background-color: $euiColorLightestShade; - } - .euiFlexGroup--gutterLarge { - margin: 0; - } - } -} - -.codeTab__projects { - .codeTab__projects--emptyHeader { - text-align: center; - } -} - -.codeSymbol { - cursor: pointer; - display: flex; - flex-shrink: 0; - align-items: center; - height: 1.5rem; - margin-left: 0.75rem; - background: transparent; - position: relative; - - .euiToken { - margin-right: $euiSizeS; - } - - > .euiIcon { - margin-right: $euiSizeXS; - } -} - -.codeSymbol--nested { - margin-left: 2rem; -} - -.code-structure-node { - padding: $euiSizeXS; -} - -.code-full-width-node { - position: absolute; - // guess there's an element has a margin 1rem affect this, can't find it sby far - width: calc(100% + 1rem); - background: $euiColorLightShade; - left: 0; - height: 1.5rem; -} - -.euiSideNavItem__items { - position: static; -} - -.codeStructureTree--icon { - margin: auto 0.25rem; - z-index: $euiZLevel2; -} - -.code-symbol-link { - &:focus { - animation: none !important; - } -} - -// EUI Overrides -// TODO: Add 'code' prefixed classnames -.euiSideNavItem--root + .euiSideNavItem--root { - margin-top: 1rem; -} - -.euiSideNavItem__items:after { - width: 0; -} - -.euiSideNavItem--trunk > .euiSideNavItem__items { - margin-left: $euiSize; -} diff --git a/x-pack/legacy/plugins/code/public/style/_utilities.scss b/x-pack/legacy/plugins/code/public/style/_utilities.scss deleted file mode 100644 index 6d0f814670e25..0000000000000 --- a/x-pack/legacy/plugins/code/public/style/_utilities.scss +++ /dev/null @@ -1,7 +0,0 @@ -.codeUtility__cursor--pointer { - cursor: pointer; -} - -.codeMargin__title { - margin: $euiSizeXS 0 $euiSize $euiSizeM; -} diff --git a/x-pack/legacy/plugins/code/public/style/_variables.scss b/x-pack/legacy/plugins/code/public/style/_variables.scss deleted file mode 100644 index c838f8d8e3309..0000000000000 --- a/x-pack/legacy/plugins/code/public/style/_variables.scss +++ /dev/null @@ -1,3 +0,0 @@ -$codeSideBarWidth: calc(#{$euiSize} * 16); -$codeSideBarIndent: $euiSizeXL; -$codeSearchBarPadding: $euiSizeS; diff --git a/x-pack/legacy/plugins/code/public/style/variables.ts b/x-pack/legacy/plugins/code/public/style/variables.ts deleted file mode 100644 index eeeba1000f575..0000000000000 --- a/x-pack/legacy/plugins/code/public/style/variables.ts +++ /dev/null @@ -1,35 +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 rem = 14; - -export function px(value: number): string { - return `${value}px`; -} - -export function percent(value: number): string { - return `${value}%`; -} - -export function pxToRem(value: number): string { - return `${value / rem}rem`; -} - -export const colors = { - textBlue: '#0079A5', - borderGrey: '#D9D9D9', - white: '#fff', - textGrey: '#3F3F3F', -}; - -export const fontSizes = { - small: '10px', - normal: '1rem', - large: '18px', - xlarge: '2rem', -}; - -export const fontFamily = 'SFProText-Regular'; diff --git a/x-pack/legacy/plugins/code/public/utils/query_string.ts b/x-pack/legacy/plugins/code/public/utils/query_string.ts deleted file mode 100644 index 6fc98f3dd4437..0000000000000 --- a/x-pack/legacy/plugins/code/public/utils/query_string.ts +++ /dev/null @@ -1,119 +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. - */ - -/** - * Copied from `ui/utils/query_string` because of NP migration. - */ - -function encodeQueryComponent(val: string, pctEncodeSpaces = false) { - return encodeURIComponent(val) - .replace(/%40/gi, '@') - .replace(/%3A/gi, ':') - .replace(/%24/g, '$') - .replace(/%2C/gi, ',') - .replace(/%20/g, pctEncodeSpaces ? '%20' : '+'); -} - -function tryDecodeURIComponent(value: string) { - try { - return decodeURIComponent(value); - } catch (e) {} // eslint-disable-line no-empty -} - -/** - * Parses an escaped url query string into key-value pairs. - * @returns {Object.} - */ -const decode = (keyValue: any) => { - const obj: { [s: string]: any } = {}; - let keyValueParts; - let key; - - (keyValue || '').split('&').forEach((keyVal: any) => { - if (keyVal) { - keyValueParts = keyVal.split('='); - key = tryDecodeURIComponent(keyValueParts[0]); - if (key !== void 0) { - const val = keyValueParts[1] !== void 0 ? tryDecodeURIComponent(keyValueParts[1]) : true; - if (!obj[key]) { - obj[key] = val; - } else if (Array.isArray(obj[key])) { - obj[key].push(val); - } else { - obj[key] = [obj[key], val]; - } - } - } - }); - return obj; -}; - -/** - * Creates a queryString out of an object - * @param {Object} obj - * @return {String} - */ -const encode = (obj: any) => { - const parts: any[] = []; - const keys = Object.keys(obj).sort(); - keys.forEach((key: any) => { - const value = obj[key]; - if (Array.isArray(value)) { - value.forEach((arrayValue: any) => { - parts.push(param(key, arrayValue)); - }); - } else { - parts.push(param(key, value)); - } - }); - return parts.length ? parts.join('&') : ''; -}; - -const param = (key: string, val: any) => { - return ( - encodeQueryComponent(key, true) + (val === true ? '' : '=' + encodeQueryComponent(val, true)) - ); -}; - -/** - * Extracts the query string from a url - * @param {String} url - * @return {Object} - returns an object describing the start/end index of the url in the string. The indices will be - * the same if the url does not have a query string - */ -const findInUrl = (url: string) => { - let qsStart = url.indexOf('?'); - let hashStart = url.lastIndexOf('#'); - - if (hashStart === -1) { - // out of bounds - hashStart = url.length; - } - - if (qsStart === -1) { - qsStart = hashStart; - } - - return { - start: qsStart, - end: hashStart, - }; -}; - -export const replaceParamInUrl = (url: string, p: string, newVal: any) => { - const loc = findInUrl(url); - const parsed = decode(url.substring(loc.start + 1, loc.end)); - - if (newVal != null) { - parsed[p] = newVal; - } else { - delete parsed[p]; - } - - const chars = url.split(''); - chars.splice(loc.start, loc.end - loc.start, '?' + encode(parsed)); - return chars.join(''); -}; diff --git a/x-pack/legacy/plugins/code/public/utils/saga_utils.ts b/x-pack/legacy/plugins/code/public/utils/saga_utils.ts deleted file mode 100644 index d9de73a68dddc..0000000000000 --- a/x-pack/legacy/plugins/code/public/utils/saga_utils.ts +++ /dev/null @@ -1,20 +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 { take, spawn } from 'redux-saga/effects'; -import { Action } from 'redux-actions'; - -function* cancelRequest(action: Action, abortController: AbortController) { - yield take(action.type); - abortController.abort(); -} - -export const singletonRequestSaga = (saga: any) => - function*(action: Action) { - const abortController = new AbortController(); - const task = yield spawn(cancelRequest, action, abortController); - yield spawn(saga, action, abortController.signal, task); - }; diff --git a/x-pack/legacy/plugins/code/public/utils/test_utils.ts b/x-pack/legacy/plugins/code/public/utils/test_utils.ts deleted file mode 100644 index e88dbafb61414..0000000000000 --- a/x-pack/legacy/plugins/code/public/utils/test_utils.ts +++ /dev/null @@ -1,64 +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 { Action, History, Location } from 'history'; -import { match } from 'react-router-dom'; - -interface LocationParam { - pathname?: string; - search?: string; - hash?: string; - state?: string; -} - -export function createLocation(location: LocationParam): Location { - return { - pathname: '', - search: '', - hash: '', - state: '', - ...location, - }; -} - -interface MatchParam { - path?: string; - url?: string; - isExact?: boolean; - params: Params; -} - -export function createMatch(m: MatchParam): match { - return { - path: '', - url: '', - isExact: true, - ...m, - }; -} - -interface HParam { - length?: number; - action: Action; - location: Location; -} - -export const mockFunction = jest.fn(); - -export function createHistory(h: HParam): History { - return { - length: 0, - push: mockFunction, - replace: mockFunction, - go: mockFunction, - goBack: mockFunction, - goForward: mockFunction, - listen: () => mockFunction, - block: () => mockFunction, - createHref: mockFunction, - ...h, - }; -} diff --git a/x-pack/legacy/plugins/code/public/utils/url.ts b/x-pack/legacy/plugins/code/public/utils/url.ts deleted file mode 100644 index 39c0a2a7f0540..0000000000000 --- a/x-pack/legacy/plugins/code/public/utils/url.ts +++ /dev/null @@ -1,11 +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 { createHashHistory } from 'history'; - -export const history = createHashHistory(); - -export const isImportRepositoryURLInvalid = (url: string) => url.trim() === ''; diff --git a/x-pack/legacy/plugins/code/scripts/_helpers.js b/x-pack/legacy/plugins/code/scripts/_helpers.js deleted file mode 100644 index 5b99a42298671..0000000000000 --- a/x-pack/legacy/plugins/code/scripts/_helpers.js +++ /dev/null @@ -1,25 +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. - */ - -const { resolve } = require('path'); - -exports.runGulpTask = function (name) { - process.chdir(resolve(__dirname, '../../../..')); - process.argv.splice(1, 1, require.resolve('gulp/bin/gulp'), name); - require('gulp/bin/gulp'); // eslint-disable-line import/no-extraneous-dependencies -}; - -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 = []) { - 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/legacy/plugins/code/scripts/all_test.js b/x-pack/legacy/plugins/code/scripts/all_test.js deleted file mode 100644 index 11f91ad75cabd..0000000000000 --- a/x-pack/legacy/plugins/code/scripts/all_test.js +++ /dev/null @@ -1,12 +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. - */ - -const execa = require('execa'); // eslint-disable-line import/no-extraneous-dependencies - -execa.sync('node', [require.resolve('./jest')], { stdio: 'inherit' }); -execa.sync('node', [require.resolve('./mocha')], { stdio: 'inherit' }); -execa.sync('node', [require.resolve('./functional_test')], { stdio: 'inherit' }); -execa.sync('node', [require.resolve('./api_integration_test')], { stdio: 'inherit' }); diff --git a/x-pack/legacy/plugins/code/scripts/api_integration_test.js b/x-pack/legacy/plugins/code/scripts/api_integration_test.js deleted file mode 100644 index e0bbb7e796d89..0000000000000 --- a/x-pack/legacy/plugins/code/scripts/api_integration_test.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. - */ - -require('./_helpers').runXPackScript('functional_tests', ['--config', 'test/api_integration/config.js', '--grep=^apis Code .*']); diff --git a/x-pack/legacy/plugins/code/scripts/check.js b/x-pack/legacy/plugins/code/scripts/check.js deleted file mode 100644 index 9c758babcb042..0000000000000 --- a/x-pack/legacy/plugins/code/scripts/check.js +++ /dev/null @@ -1,10 +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. - */ - -const execa = require('execa'); // eslint-disable-line import/no-extraneous-dependencies - -execa.sync('node', [require.resolve('./lint')], { stdio: 'inherit' }); -execa.sync('node', [require.resolve('./type_check')], { stdio: 'inherit' }); diff --git a/x-pack/legacy/plugins/code/scripts/functional_test.js b/x-pack/legacy/plugins/code/scripts/functional_test.js deleted file mode 100644 index 978b432340472..0000000000000 --- a/x-pack/legacy/plugins/code/scripts/functional_test.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. - */ - -require('./_helpers').runXPackScript('functional_tests', ['--config', 'test/functional/config.js', '--grep=^Code .*']); diff --git a/x-pack/legacy/plugins/code/scripts/jest.js b/x-pack/legacy/plugins/code/scripts/jest.js deleted file mode 100644 index fbd4e3f971815..0000000000000 --- a/x-pack/legacy/plugins/code/scripts/jest.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. - */ - -require('./_helpers').runXPackScript('jest', ['legacy/plugins/code']); diff --git a/x-pack/legacy/plugins/code/scripts/lint.js b/x-pack/legacy/plugins/code/scripts/lint.js deleted file mode 100644 index 393b9088b15e2..0000000000000 --- a/x-pack/legacy/plugins/code/scripts/lint.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. - */ - -require('./_helpers').runKibanaScript('eslint', ['x-pack/legacy/plugins/code/**/*.{js,jsx,ts,tsx}', '--fix']); diff --git a/x-pack/legacy/plugins/code/scripts/mocha.js b/x-pack/legacy/plugins/code/scripts/mocha.js deleted file mode 100644 index dd543f32e5d40..0000000000000 --- a/x-pack/legacy/plugins/code/scripts/mocha.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. - */ - -require('./_helpers').runKibanaScript('mocha', ['x-pack/legacy/plugins/code/server/__tests__/*.{ts,tsx}']); diff --git a/x-pack/legacy/plugins/code/scripts/test.js b/x-pack/legacy/plugins/code/scripts/test.js deleted file mode 100644 index 6eed94bd2f912..0000000000000 --- a/x-pack/legacy/plugins/code/scripts/test.js +++ /dev/null @@ -1,10 +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. - */ - -const execa = require('execa'); // eslint-disable-line import/no-extraneous-dependencies - -execa.sync('node', [require.resolve('./jest')], { stdio: 'inherit' }); -execa.sync('node', [require.resolve('./mocha')], { stdio: 'inherit' }); diff --git a/x-pack/legacy/plugins/code/scripts/type_check.js b/x-pack/legacy/plugins/code/scripts/type_check.js deleted file mode 100644 index 974978810e1d8..0000000000000 --- a/x-pack/legacy/plugins/code/scripts/type_check.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. - */ - -require('./_helpers').runKibanaScript('type_check', ['--project', 'x-pack/tsconfig.json']); diff --git a/x-pack/legacy/plugins/code/server/__tests__/clone_worker.ts b/x-pack/legacy/plugins/code/server/__tests__/clone_worker.ts deleted file mode 100644 index 09a039636bb4e..0000000000000 --- a/x-pack/legacy/plugins/code/server/__tests__/clone_worker.ts +++ /dev/null @@ -1,478 +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 assert from 'assert'; -import { delay } from 'bluebird'; -import path from 'path'; -import rimraf from 'rimraf'; -import sinon from 'sinon'; -import { prepareProjectByCloning as prepareProject } from '../test_utils'; -import { CloneWorkerResult, Repository } from '../../model'; -import { DiskWatermarkService } from '../disk_watermark'; -import { GitOperations } from '../git_operations'; -import { EsClient, Esqueue } from '../lib/esqueue'; -import { Logger } from '../log'; -import { CloneWorker, IndexWorker } from '../queue'; -import { CancellationReason, CancellationSerivce } from '../queue/cancellation_service'; -import { RepositoryServiceFactory } from '../repository_service_factory'; -import { createTestServerOption, emptyAsyncFunc } from '../test_utils'; -import { ConsoleLoggerFactory } from '../utils/console_logger_factory'; - -const log: Logger = new ConsoleLoggerFactory().getLogger(['test']); - -const esQueue = {}; - -const serverOptions = createTestServerOption(); -const gitOps = new GitOperations(serverOptions.repoPath); - -function cleanWorkspace() { - return new Promise(resolve => { - rimraf(serverOptions.workspacePath, resolve); - }); -} - -describe('clone_worker_tests', () => { - // @ts-ignore - before(async () => { - return new Promise(resolve => { - rimraf(serverOptions.repoPath, resolve); - }); - }); - - beforeEach(async function() { - // @ts-ignore - this.timeout(200000); - await prepareProject( - 'https://github.com/Microsoft/TypeScript-Node-Starter.git', - path.join(serverOptions.repoPath, 'github.com/Microsoft/TypeScript-Node-Starter') - ); - }); - // @ts-ignore - after(() => { - return cleanWorkspace(); - }); - - afterEach(() => { - sinon.restore(); - }); - - it('Execute clone job', async () => { - // Setup RepositoryService - const cloneSpy = sinon.spy(); - const repoService = { - clone: emptyAsyncFunc, - }; - repoService.clone = cloneSpy; - const repoServiceFactory = { - newInstance: (): void => { - return; - }, - }; - const newInstanceSpy = sinon.fake.returns(repoService); - repoServiceFactory.newInstance = newInstanceSpy; - - // Setup CancellationService - const cancelCloneJobSpy = sinon.spy(); - const registerCancelableCloneJobSpy = sinon.spy(); - const cancellationService: any = { - cancelCloneJob: emptyAsyncFunc, - registerCancelableCloneJob: emptyAsyncFunc, - }; - cancellationService.cancelCloneJob = cancelCloneJobSpy; - cancellationService.registerCancelableCloneJob = registerCancelableCloneJobSpy; - - // Setup DiskWatermarkService - const isLowWatermarkSpy = sinon.stub().resolves(false); - const diskWatermarkService: any = { - isLowWatermark: isLowWatermarkSpy, - }; - - const cloneWorker = new CloneWorker( - esQueue as Esqueue, - log, - {} as EsClient, - serverOptions, - gitOps, - {} as IndexWorker, - (repoServiceFactory as any) as RepositoryServiceFactory, - cancellationService as CancellationSerivce, - diskWatermarkService as DiskWatermarkService - ); - - await cloneWorker.executeJob({ - payload: { - url: 'https://github.com/Microsoft/TypeScript-Node-Starter.git', - }, - options: {}, - timestamp: 0, - }); - - assert.ok(isLowWatermarkSpy.calledOnce); - assert.ok(newInstanceSpy.calledOnce); - assert.ok(cloneSpy.calledOnce); - }); - - it('On clone job completed.', async () => { - // Setup IndexWorker - const enqueueJobSpy = sinon.spy(); - const indexWorker = { - enqueueJob: emptyAsyncFunc, - }; - indexWorker.enqueueJob = enqueueJobSpy; - - // Setup EsClient - const updateSpy = sinon.spy(); - const esClient = { - update: emptyAsyncFunc, - }; - esClient.update = updateSpy; - - // Setup CancellationService - const cancelCloneJobSpy = sinon.spy(); - const registerCancelableCloneJobSpy = sinon.spy(); - const cancellationService: any = { - cancelCloneJob: emptyAsyncFunc, - registerCancelableCloneJob: emptyAsyncFunc, - }; - cancellationService.cancelCloneJob = cancelCloneJobSpy; - cancellationService.registerCancelableCloneJob = registerCancelableCloneJobSpy; - - // Setup DiskWatermarkService - const isLowWatermarkSpy = sinon.stub().resolves(false); - const diskWatermarkService: any = { - isLowWatermark: isLowWatermarkSpy, - }; - - const cloneWorker = new CloneWorker( - esQueue as Esqueue, - log, - esClient as EsClient, - serverOptions, - gitOps, - (indexWorker as any) as IndexWorker, - {} as RepositoryServiceFactory, - cancellationService as CancellationSerivce, - diskWatermarkService as DiskWatermarkService - ); - - await cloneWorker.onJobCompleted( - { - payload: { - url: 'https://github.com/Microsoft/TypeScript-Node-Starter.git', - }, - options: {}, - timestamp: 0, - }, - { - uri: 'github.com/Microsoft/TypeScript-Node-Starter', - repo: ({ - uri: 'github.com/Microsoft/TypeScript-Node-Starter', - } as any) as Repository, - } - ); - - // EsClient update got called 3 times: - // * update default branch and revision of a repository object - // * update the revision in the git clone status - // * update the clone progress - assert.ok(updateSpy.calledThrice); - - // Index request is issued after a 1s delay. - await delay(1000); - assert.ok(enqueueJobSpy.calledOnce); - - assert.ok(isLowWatermarkSpy.notCalled); - }); - - it('On clone job completed because of cancellation', async () => { - // Setup IndexWorker - const enqueueJobSpy = sinon.spy(); - const indexWorker = { - enqueueJob: emptyAsyncFunc, - }; - indexWorker.enqueueJob = enqueueJobSpy; - - // Setup EsClient - const updateSpy = sinon.spy(); - const esClient = { - update: emptyAsyncFunc, - }; - esClient.update = updateSpy; - - // Setup CancellationService - const cancelCloneJobSpy = sinon.spy(); - const registerCancelableCloneJobSpy = sinon.spy(); - const cancellationService: any = { - cancelCloneJob: emptyAsyncFunc, - registerCancelableCloneJob: emptyAsyncFunc, - }; - cancellationService.cancelCloneJob = cancelCloneJobSpy; - cancellationService.registerCancelableCloneJob = registerCancelableCloneJobSpy; - - // Setup DiskWatermarkService - const isLowWatermarkSpy = sinon.stub().resolves(false); - const diskWatermarkService: any = { - isLowWatermark: isLowWatermarkSpy, - }; - - const cloneWorker = new CloneWorker( - esQueue as Esqueue, - log, - esClient as EsClient, - serverOptions, - gitOps, - (indexWorker as any) as IndexWorker, - {} as RepositoryServiceFactory, - cancellationService as CancellationSerivce, - diskWatermarkService as DiskWatermarkService - ); - - await cloneWorker.onJobCompleted( - { - payload: { - url: 'https://github.com/Microsoft/TypeScript-Node-Starter.git', - }, - options: {}, - timestamp: 0, - }, - { - uri: 'github.com/Microsoft/TypeScript-Node-Starter', - repo: ({ - uri: 'github.com/Microsoft/TypeScript-Node-Starter', - } as any) as Repository, - cancelled: true, - } - ); - - // EsClient update should not be called for the sake of clone - // cancellation. - assert.ok(updateSpy.notCalled); - - // Index request should not be issued after clone request is done. - await delay(1000); - assert.ok(enqueueJobSpy.notCalled); - - assert.ok(isLowWatermarkSpy.notCalled); - }); - - it('On clone job enqueued.', async () => { - // Setup EsClient - const indexSpy = sinon.spy(); - const esClient = { - index: emptyAsyncFunc, - }; - esClient.index = indexSpy; - - // Setup CancellationService - const cancelCloneJobSpy = sinon.spy(); - const registerCancelableCloneJobSpy = sinon.spy(); - const cancellationService: any = { - cancelCloneJob: emptyAsyncFunc, - registerCancelableCloneJob: emptyAsyncFunc, - }; - cancellationService.cancelCloneJob = cancelCloneJobSpy; - cancellationService.registerCancelableCloneJob = registerCancelableCloneJobSpy; - - // Setup DiskWatermarkService - const isLowWatermarkSpy = sinon.stub().resolves(false); - const diskWatermarkService: any = { - isLowWatermark: isLowWatermarkSpy, - }; - - const cloneWorker = new CloneWorker( - esQueue as Esqueue, - log, - (esClient as any) as EsClient, - serverOptions, - gitOps, - {} as IndexWorker, - {} as RepositoryServiceFactory, - cancellationService as CancellationSerivce, - diskWatermarkService as DiskWatermarkService - ); - - await cloneWorker.onJobEnqueued({ - payload: { - url: 'https://github.com/Microsoft/TypeScript-Node-Starter.git', - }, - options: {}, - timestamp: 0, - }); - - // Expect EsClient index to be called to update the progress to 0. - assert.ok(indexSpy.calledOnce); - }); - - it('Skip clone job for invalid git url', async () => { - // Setup RepositoryService - const cloneSpy = sinon.spy(); - const repoService = { - clone: emptyAsyncFunc, - }; - repoService.clone = cloneSpy; - const repoServiceFactory = { - newInstance: (): void => { - return; - }, - }; - const newInstanceSpy = sinon.fake.returns(repoService); - repoServiceFactory.newInstance = newInstanceSpy; - - // Setup CancellationService - const cancelCloneJobSpy = sinon.spy(); - const registerCancelableCloneJobSpy = sinon.spy(); - const cancellationService: any = { - cancelCloneJob: emptyAsyncFunc, - registerCancelableCloneJob: emptyAsyncFunc, - }; - cancellationService.cancelCloneJob = cancelCloneJobSpy; - cancellationService.registerCancelableCloneJob = registerCancelableCloneJobSpy; - - // Setup DiskWatermarkService - const isLowWatermarkSpy = sinon.stub().resolves(false); - const diskWatermarkService: any = { - isLowWatermark: isLowWatermarkSpy, - }; - - const cloneWorker = new CloneWorker( - esQueue as Esqueue, - log, - {} as EsClient, - serverOptions, - gitOps, - {} as IndexWorker, - (repoServiceFactory as any) as RepositoryServiceFactory, - cancellationService as CancellationSerivce, - diskWatermarkService as DiskWatermarkService - ); - - const result1 = (await cloneWorker.executeJob({ - payload: { - url: 'file:///foo/bar.git', - }, - options: {}, - timestamp: 0, - })) as CloneWorkerResult; - - assert.ok(!result1.repo); - assert.ok(newInstanceSpy.notCalled); - assert.ok(cloneSpy.notCalled); - assert.ok(isLowWatermarkSpy.calledOnce); - - const result2 = (await cloneWorker.executeJob({ - payload: { - url: '/foo/bar.git', - }, - options: {}, - timestamp: 0, - })) as CloneWorkerResult; - - assert.ok(!result2.repo); - assert.ok(newInstanceSpy.notCalled); - assert.ok(cloneSpy.notCalled); - assert.ok(isLowWatermarkSpy.calledTwice); - }); - - it('Execute clone job failed because of low available disk space', async () => { - // Setup RepositoryService - const cloneSpy = sinon.spy(); - const repoService = { - clone: emptyAsyncFunc, - }; - repoService.clone = cloneSpy; - const repoServiceFactory = { - newInstance: (): void => { - return; - }, - }; - const newInstanceSpy = sinon.fake.returns(repoService); - repoServiceFactory.newInstance = newInstanceSpy; - - // Setup CancellationService - const cancelCloneJobSpy = sinon.spy(); - const registerCancelableCloneJobSpy = sinon.spy(); - const cancellationService: any = { - cancelCloneJob: emptyAsyncFunc, - registerCancelableCloneJob: emptyAsyncFunc, - }; - cancellationService.cancelCloneJob = cancelCloneJobSpy; - cancellationService.registerCancelableCloneJob = registerCancelableCloneJobSpy; - - // Setup DiskWatermarkService - const isLowWatermarkSpy = sinon.stub().resolves(true); - const diskWatermarkService: any = { - isLowWatermark: isLowWatermarkSpy, - diskWatermarkViolationMessage: sinon.stub().returns('No enough disk space'), - }; - - // Setup EsClient - const updateSpy = sinon.spy(); - const esClient = { - update: emptyAsyncFunc, - }; - esClient.update = updateSpy; - - // Setup IndexWorker - const enqueueJobSpy = sinon.spy(); - const indexWorker = { - enqueueJob: emptyAsyncFunc, - }; - indexWorker.enqueueJob = enqueueJobSpy; - - const cloneWorker = new CloneWorker( - esQueue as Esqueue, - log, - esClient as EsClient, - serverOptions, - gitOps, - (indexWorker as any) as IndexWorker, - (repoServiceFactory as any) as RepositoryServiceFactory, - cancellationService as CancellationSerivce, - diskWatermarkService as DiskWatermarkService - ); - - let res: CloneWorkerResult = { uri: 'github.com/Microsoft/TypeScript-Node-Starter' }; - try { - res = (await cloneWorker.executeJob({ - payload: { - url: 'https://github.com/Microsoft/TypeScript-Node-Starter.git', - }, - options: {}, - timestamp: 0, - })) as CloneWorkerResult; - // This step should not be touched. - assert.ok(false); - } catch (error) { - assert.ok(isLowWatermarkSpy.calledOnce); - assert.ok(newInstanceSpy.notCalled); - assert.ok(cloneSpy.notCalled); - } - - assert.ok(res.cancelled); - assert.ok(res.cancelledReason === CancellationReason.LOW_DISK_SPACE); - - const onJobExecutionErrorSpy = sinon.spy(); - cloneWorker.onJobExecutionError = onJobExecutionErrorSpy; - - await cloneWorker.onJobCompleted( - { - payload: { - url: 'https://github.com/Microsoft/TypeScript-Node-Starter.git', - }, - options: {}, - timestamp: 0, - }, - res - ); - - assert.ok(onJobExecutionErrorSpy.calledOnce); - // Non of the follow up steps of a normal complete job should not be called - // because the job is going to be forwarded as execution error. - assert.ok(updateSpy.notCalled); - await delay(1000); - assert.ok(enqueueJobSpy.notCalled); - }); -}); diff --git a/x-pack/legacy/plugins/code/server/__tests__/commit_indexer.ts b/x-pack/legacy/plugins/code/server/__tests__/commit_indexer.ts deleted file mode 100644 index c8fd117ec8a97..0000000000000 --- a/x-pack/legacy/plugins/code/server/__tests__/commit_indexer.ts +++ /dev/null @@ -1,198 +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 assert from 'assert'; -import path from 'path'; -import rimraf from 'rimraf'; -import sinon from 'sinon'; -import { prepareProjectByCloning as prepareProject } from '../test_utils'; -import { GitOperations } from '../git_operations'; -import { CommitIndexRequest, WorkerReservedProgress } from '../../model'; -import { CommitIndexer } from '../indexer/commit_indexer'; -import { RepositoryGitStatusReservedField } from '../indexer/schema'; -import { EsClient } from '../lib/esqueue'; -import { Logger } from '../log'; -import { createTestServerOption, emptyAsyncFunc } from '../test_utils'; -import { ConsoleLoggerFactory } from '../utils/console_logger_factory'; - -const log: Logger = new ConsoleLoggerFactory().getLogger(['test']); - -const esClient = { - bulk: emptyAsyncFunc, - get: emptyAsyncFunc, - deleteByQuery: emptyAsyncFunc, - indices: { - existsAlias: emptyAsyncFunc, - create: emptyAsyncFunc, - putAlias: emptyAsyncFunc, - }, -}; - -const repoUri = 'github.com/elastic/TypeScript-Node-Starter'; - -const serverOptions = createTestServerOption(); -const gitOps = new GitOperations(serverOptions.repoPath); - -function cleanWorkspace() { - return new Promise(resolve => { - rimraf(serverOptions.workspacePath, resolve); - }); -} - -function setupEsClientSpy() { - // Mock a git status of the repo indicating the the repo is fully cloned already. - const getSpy = sinon.fake.returns( - Promise.resolve({ - _source: { - [RepositoryGitStatusReservedField]: { - uri: repoUri, - progress: WorkerReservedProgress.COMPLETED, - timestamp: new Date(), - cloneProgress: { - isCloned: true, - }, - }, - }, - }) - ); - const existsAliasSpy = sinon.fake.returns(false); - const createSpy = sinon.spy(); - const putAliasSpy = sinon.spy(); - const deleteByQuerySpy = sinon.spy(); - const bulkSpy = sinon.spy(); - esClient.bulk = bulkSpy; - esClient.indices.existsAlias = existsAliasSpy; - esClient.indices.create = createSpy; - esClient.indices.putAlias = putAliasSpy; - esClient.get = getSpy; - esClient.deleteByQuery = deleteByQuerySpy; - return { - getSpy, - existsAliasSpy, - createSpy, - putAliasSpy, - deleteByQuerySpy, - bulkSpy, - }; -} - -describe('Commit indexer unit tests', function(this: any) { - this.timeout(20000); - - // @ts-ignore - before(async () => { - return new Promise(resolve => { - rimraf(serverOptions.repoPath, resolve); - }); - }); - - beforeEach(async function() { - // @ts-ignore - this.timeout(200000); - return await prepareProject( - `https://${repoUri}.git`, - path.join(serverOptions.repoPath, repoUri) - ); - }); - // @ts-ignore - after(() => { - return cleanWorkspace(); - }); - - afterEach(() => { - sinon.restore(); - }); - - it('Normal COMMIT index process.', async () => { - // Setup the esClient spies - const { - existsAliasSpy, - createSpy, - putAliasSpy, - deleteByQuerySpy, - bulkSpy, - } = setupEsClientSpy(); - - const indexer = new CommitIndexer(repoUri, 'master', gitOps, esClient as EsClient, log); - await indexer.start(); - - assert.strictEqual(deleteByQuerySpy.callCount, 1); - assert.strictEqual(existsAliasSpy.callCount, 1); - assert.strictEqual(createSpy.callCount, 1); - assert.strictEqual(putAliasSpy.callCount, 1); - - assert.strictEqual(bulkSpy.callCount, 1); - let total = 0; - for (let i = 0; i < bulkSpy.callCount; i++) { - total += bulkSpy.getCall(i).args[0].body.length; - } - assert.strictEqual(total, 147 * 2); - // @ts-ignore - }).timeout(20000); - - it('Cancel COMMIT index process.', async () => { - // Setup the esClient spies - const { - existsAliasSpy, - createSpy, - putAliasSpy, - deleteByQuerySpy, - bulkSpy, - } = setupEsClientSpy(); - - const indexer = new CommitIndexer(repoUri, 'master', gitOps, esClient as EsClient, log); - // Cancel the indexer before start. - indexer.cancel(); - await indexer.start(); - - assert.strictEqual(deleteByQuerySpy.callCount, 1); - assert.strictEqual(existsAliasSpy.callCount, 1); - assert.strictEqual(createSpy.callCount, 1); - assert.strictEqual(putAliasSpy.callCount, 1); - - // Because the indexer is cancelled already in the begining. 0 doc should be - // indexed and thus bulk won't be called. - assert.ok(bulkSpy.notCalled); - }); - - it('Index continues from a checkpoint', async () => { - // Setup the esClient spies - const { - existsAliasSpy, - createSpy, - putAliasSpy, - deleteByQuerySpy, - bulkSpy, - } = setupEsClientSpy(); - - const indexer = new CommitIndexer(repoUri, 'master', gitOps, esClient as EsClient, log); - // Apply a checkpoint in here. - await indexer.start(undefined, ({ - repoUri: '', - revision: 'master', - commit: { - // The direct parent of the HEAD - id: '46971a8454761f1a11d8fde4d96ff8d29bc4e754', - }, - } as any) as CommitIndexRequest); - - // Expect EsClient deleteByQuery called 0 times for repository cleaning while - // dealing with repository checkpoint. - assert.strictEqual(deleteByQuerySpy.callCount, 0); - - // Ditto for index and alias creation - assert.strictEqual(existsAliasSpy.callCount, 0); - assert.strictEqual(createSpy.callCount, 0); - assert.strictEqual(putAliasSpy.callCount, 0); - assert.strictEqual(bulkSpy.callCount, 1); - let total = 0; - for (let i = 0; i < bulkSpy.callCount; i++) { - total += bulkSpy.getCall(i).args[0].body.length; - } - assert.strictEqual(total, 146 * 2); - // @ts-ignore - }).timeout(20000); -}); diff --git a/x-pack/legacy/plugins/code/server/__tests__/git_operations.ts b/x-pack/legacy/plugins/code/server/__tests__/git_operations.ts deleted file mode 100644 index 9391a8aafc131..0000000000000 --- a/x-pack/legacy/plugins/code/server/__tests__/git_operations.ts +++ /dev/null @@ -1,114 +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. - */ - -// @ts-ignore -import assert from 'assert'; -import { execSync } from 'child_process'; -import fs from 'fs'; -import path from 'path'; -import rimraf from 'rimraf'; -import { GitOperations } from '../git_operations'; -import { createTestServerOption } from '../test_utils'; -import { prepareProjectByCloning as cloneProject, prepareProjectByInit } from '../test_utils'; - -describe('git_operations', () => { - it('get default branch from a non master repo', async () => { - const repoUri = 'github.com/foo/bar'; - const repoDir = path.join(serverOptions.repoPath, repoUri); - fs.mkdirSync(repoDir, { recursive: true }); - - // create a non-master using git commands - const shell = ` - git init - git add 'run.sh' - git commit -m 'init commit' - git branch -m trunk - `; - fs.writeFileSync(path.join(repoDir, 'run.sh'), shell, 'utf-8'); - execSync('sh ./run.sh', { - cwd: repoDir, - }); - - try { - const g = new GitOperations(serverOptions.repoPath); - const defaultBranch = await g.getDefaultBranch(repoUri); - assert.strictEqual(defaultBranch, 'trunk'); - const headRevision = await g.getHeadRevision(repoUri); - const headCommit = await g.getCommitInfo(repoUri, 'HEAD'); - assert.strictEqual(headRevision, headCommit!.id); - } finally { - rimraf.sync(repoDir); - } - }); - - async function prepareProject(repoPath: string) { - fs.mkdirSync(repoPath, { recursive: true }); - const workDir = path.join(serverOptions.workspacePath, repoUri); - const { git, commits } = await prepareProjectByInit(workDir, { - 'commit for test': { - '1': '', - 'src/2': '', - 'src/3': '', - }, - }); - // eslint-disable-next-line no-console - console.log(`created commit ${commits[0]}`); - await git.clone(workDir, repoPath, ['--bare']); - } - - // @ts-ignore - before(async function() { - // @ts-ignore - this.timeout(200000); - await prepareProject(path.join(serverOptions.repoPath, repoUri)); - await cloneProject( - 'https://github.com/elastic/TypeScript-Node-Starter.git', - path.join(serverOptions.repoPath, 'github.com/elastic/TypeScript-Node-Starter') - ); - }); - const repoUri = 'github.com/test/test_repo'; - - const serverOptions = createTestServerOption(); - - it('can iterate a repo', async () => { - const g = new GitOperations(serverOptions.repoPath); - let count = 0; - const iterator = await g.iterateRepo(repoUri, 'HEAD'); - for await (const value of iterator) { - if (count === 0) { - assert.strictEqual('1', value.path); - } else if (count === 1) { - assert.strictEqual('src/2', value.path); - } else if (count === 2) { - assert.strictEqual('src/3', value.path); - } else { - assert.fail('this repo should contains exactly 3 files'); - } - count++; - } - const totalFiles = await g.countRepoFiles(repoUri, 'HEAD'); - assert.strictEqual(count, 3, 'this repo should contains exactly 3 files'); - assert.strictEqual(totalFiles, 3, 'this repo should contains exactly 3 files'); - }); - - it('can resolve branches', async () => { - const g = new GitOperations(serverOptions.repoPath); - const c = await g.getCommitOr404('github.com/elastic/TypeScript-Node-Starter', 'master'); - assert.strictEqual(c.id, '261557d657fdfddf78119d15d38b1f6a7be005ed'); - const c1 = await g.getCommitOr404('github.com/elastic/TypeScript-Node-Starter', 'VS'); - assert.strictEqual(c1.id, 'ba73782df210e0a7744ac9b623d58081a1801738'); - // @ts-ignore - }).timeout(100000); - - it('get diff between arbitrary 2 revisions', async () => { - const g = new GitOperations(serverOptions.repoPath); - const d = await g.getDiff('github.com/elastic/TypeScript-Node-Starter', '6206f643', '4779cb7e'); - assert.equal(d.additions, 2); - assert.equal(d.deletions, 4); - assert.equal(d.files.length, 3); - // @ts-ignore - }).timeout(100000); -}); diff --git a/x-pack/legacy/plugins/code/server/__tests__/lsp_incremental_indexer.ts b/x-pack/legacy/plugins/code/server/__tests__/lsp_incremental_indexer.ts deleted file mode 100644 index aafdf8ebce85b..0000000000000 --- a/x-pack/legacy/plugins/code/server/__tests__/lsp_incremental_indexer.ts +++ /dev/null @@ -1,297 +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 assert from 'assert'; -import path from 'path'; -import rimraf from 'rimraf'; -import sinon from 'sinon'; - -import { DiffKind } from '../../common/git_diff'; -import { WorkerReservedProgress } from '../../model'; -import { GitOperations } from '../git_operations'; -import { LspIncrementalIndexer } from '../indexer/lsp_incremental_indexer'; -import { RepositoryGitStatusReservedField } from '../indexer/schema'; -import { EsClient } from '../lib/esqueue'; -import { Logger } from '../log'; -import { InstallManager } from '../lsp/install_manager'; -import { LspService } from '../lsp/lsp_service'; -import { RepositoryConfigController } from '../repository_config_controller'; -import { createTestServerOption, emptyAsyncFunc, createTestHapiServer } from '../test_utils'; -import { ConsoleLoggerFactory } from '../utils/console_logger_factory'; -import { prepareProjectByCloning as prepareProject } from '../test_utils'; - -const log: Logger = new ConsoleLoggerFactory().getLogger(['test']); - -const esClient = { - bulk: emptyAsyncFunc, - get: emptyAsyncFunc, - deleteByQuery: emptyAsyncFunc, - indices: { - existsAlias: emptyAsyncFunc, - create: emptyAsyncFunc, - putAlias: emptyAsyncFunc, - }, -}; - -const repoUri = 'github.com/elastic/TypeScript-Node-Starter'; - -const serverOptions = createTestServerOption(); -const server = createTestHapiServer(); -const gitOps = new GitOperations(serverOptions.repoPath); - -function cleanWorkspace() { - return new Promise(resolve => { - rimraf(serverOptions.workspacePath, resolve); - }); -} - -function setupEsClientSpy() { - // Mock a git status of the repo indicating the the repo is fully cloned already. - const getSpy = sinon.fake.returns( - Promise.resolve({ - _source: { - [RepositoryGitStatusReservedField]: { - uri: 'github.com/elastic/TypeScript-Node-Starter', - progress: WorkerReservedProgress.COMPLETED, - timestamp: new Date(), - cloneProgress: { - isCloned: true, - }, - }, - }, - }) - ); - const existsAliasSpy = sinon.fake.returns(false); - const createSpy = sinon.spy(); - const putAliasSpy = sinon.spy(); - const deleteByQuerySpy = sinon.spy(); - const bulkSpy = sinon.spy(); - esClient.bulk = bulkSpy; - esClient.indices.existsAlias = existsAliasSpy; - esClient.indices.create = createSpy; - esClient.indices.putAlias = putAliasSpy; - esClient.get = getSpy; - esClient.deleteByQuery = deleteByQuerySpy; - return { - getSpy, - existsAliasSpy, - createSpy, - putAliasSpy, - deleteByQuerySpy, - bulkSpy, - }; -} - -function setupLsServiceSendRequestSpy(): sinon.SinonSpy { - return sinon.fake.returns( - Promise.resolve({ - result: [ - { - // 1 mock symbol for each file - symbols: [ - { - symbolInformation: { - name: 'mocksymbolname', - }, - }, - ], - // 1 mock reference for each file - references: [{}], - }, - ], - }) - ); -} - -describe('LSP incremental indexer unit tests', () => { - // @ts-ignore - before(async () => { - return new Promise(resolve => { - rimraf(serverOptions.repoPath, resolve); - }); - }); - - beforeEach(async function() { - // @ts-ignore - this.timeout(200000); - return await prepareProject( - 'https://github.com/elastic/TypeScript-Node-Starter.git', - path.join(serverOptions.repoPath, repoUri) - ); - }); - // @ts-ignore - after(() => { - return cleanWorkspace(); - }); - - afterEach(() => { - sinon.restore(); - }); - - it('Normal LSP index process.', async () => { - // Setup the esClient spies - const { - existsAliasSpy, - createSpy, - putAliasSpy, - deleteByQuerySpy, - bulkSpy, - } = setupEsClientSpy(); - - const lspservice = new LspService( - '127.0.0.1', - serverOptions, - gitOps, - esClient as EsClient, - new InstallManager(server, serverOptions), - new ConsoleLoggerFactory(), - new RepositoryConfigController(esClient as EsClient) - ); - - lspservice.sendRequest = setupLsServiceSendRequestSpy(); - - const indexer = new LspIncrementalIndexer( - 'github.com/elastic/TypeScript-Node-Starter', - 'HEAD', - '67002808', - lspservice, - serverOptions, - gitOps, - esClient as EsClient, - log - ); - await indexer.start(); - - // Index and alias creation are not necessary for incremental indexing - assert.strictEqual(existsAliasSpy.callCount, 0); - assert.strictEqual(createSpy.callCount, 0); - assert.strictEqual(putAliasSpy.callCount, 0); - - // DeletebyQuery is called 10 times (1 file + 1 symbol reuqests per diff item) - // for 5 MODIFIED items - assert.strictEqual(deleteByQuerySpy.callCount, 5); - - // There are 5 MODIFIED items and 1 ADDED item. Only 1 file is in supported - // language. Each file with supported language has 1 file + 1 symbol + 1 reference. - // Total doc indexed should be 6 * 2 + 4 = 16, - // which can be fitted into a single batch index. - assert.strictEqual(bulkSpy.callCount, 2); - let total = 0; - for (let i = 0; i < bulkSpy.callCount; i++) { - total += bulkSpy.getCall(i).args[0].body.length; - } - assert.strictEqual(total, 8); - - // @ts-ignore - }).timeout(20000); - - it('Cancel LSP index process.', async () => { - // Setup the esClient spies - const { - existsAliasSpy, - createSpy, - putAliasSpy, - deleteByQuerySpy, - bulkSpy, - } = setupEsClientSpy(); - - const lspservice = new LspService( - '127.0.0.1', - serverOptions, - gitOps, - esClient as EsClient, - new InstallManager(server, serverOptions), - new ConsoleLoggerFactory(), - new RepositoryConfigController(esClient as EsClient) - ); - - lspservice.sendRequest = setupLsServiceSendRequestSpy(); - - const indexer = new LspIncrementalIndexer( - 'github.com/elastic/TypeScript-Node-Starter', - 'HEAD', - '67002808', - lspservice, - serverOptions, - gitOps, - esClient as EsClient, - log - ); - // Cancel the indexer before start. - indexer.cancel(); - await indexer.start(); - - // Index and alias creation are not necessary for incremental indexing. - assert.strictEqual(existsAliasSpy.callCount, 0); - assert.strictEqual(createSpy.callCount, 0); - assert.strictEqual(putAliasSpy.callCount, 0); - - // Because the indexer is cancelled already in the begining. 0 doc should be - // indexed and thus bulk and deleteByQuery won't be called. - assert.ok(bulkSpy.notCalled); - assert.ok(deleteByQuerySpy.notCalled); - }); - - it('Index continues from a checkpoint', async () => { - // Setup the esClient spies - const { - existsAliasSpy, - createSpy, - putAliasSpy, - deleteByQuerySpy, - bulkSpy, - } = setupEsClientSpy(); - - const lspservice = new LspService( - '127.0.0.1', - serverOptions, - gitOps, - esClient as EsClient, - new InstallManager(server, serverOptions), - new ConsoleLoggerFactory(), - new RepositoryConfigController(esClient as EsClient) - ); - - lspservice.sendRequest = setupLsServiceSendRequestSpy(); - - const indexer = new LspIncrementalIndexer( - 'github.com/elastic/TypeScript-Node-Starter', - 'HEAD', - '67002808', - lspservice, - serverOptions, - gitOps, - esClient as EsClient, - log - ); - - // Apply a checkpoint in here. - await indexer.start(undefined, { - repoUri: '', - filePath: 'package.json', - revision: 'HEAD', - originRevision: '67002808', - kind: DiffKind.MODIFIED, - }); - - // Index and alias creation are not necessary for incremental indexing. - assert.strictEqual(existsAliasSpy.callCount, 0); - assert.strictEqual(createSpy.callCount, 0); - assert.strictEqual(putAliasSpy.callCount, 0); - - // There are 2 items after the checkpoint. No items is in supported language. - // Total doc indexed should be 3 * 1 = 3, which can be fitted into a single batch index. - assert.strictEqual(bulkSpy.callCount, 2); - let total = 0; - for (let i = 0; i < bulkSpy.callCount; i++) { - total += bulkSpy.getCall(i).args[0].body.length; - } - assert.strictEqual(total, 4 * 2); - assert.strictEqual(deleteByQuerySpy.callCount, 1); - // @ts-ignore - }).timeout(20000); - // @ts-ignore -}).timeout(20000); diff --git a/x-pack/legacy/plugins/code/server/__tests__/lsp_indexer.ts b/x-pack/legacy/plugins/code/server/__tests__/lsp_indexer.ts deleted file mode 100644 index ec7e539d1912e..0000000000000 --- a/x-pack/legacy/plugins/code/server/__tests__/lsp_indexer.ts +++ /dev/null @@ -1,320 +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 assert from 'assert'; -import path from 'path'; -import rimraf from 'rimraf'; -import sinon from 'sinon'; -import { prepareProjectByCloning as prepareProject } from '../test_utils'; - -import { GitOperations } from '../git_operations'; -import { WorkerReservedProgress } from '../../model'; -import { LspIndexer } from '../indexer/lsp_indexer'; -import { RepositoryGitStatusReservedField } from '../indexer/schema'; -import { EsClient } from '../lib/esqueue'; -import { Logger } from '../log'; -import { InstallManager } from '../lsp/install_manager'; -import { LspService } from '../lsp/lsp_service'; -import { RepositoryConfigController } from '../repository_config_controller'; -import { createTestHapiServer, createTestServerOption, emptyAsyncFunc } from '../test_utils'; -import { ConsoleLoggerFactory } from '../utils/console_logger_factory'; - -const log: Logger = new ConsoleLoggerFactory().getLogger(['test']); - -const esClient = { - bulk: emptyAsyncFunc, - get: emptyAsyncFunc, - deleteByQuery: emptyAsyncFunc, - indices: { - existsAlias: emptyAsyncFunc, - create: emptyAsyncFunc, - putAlias: emptyAsyncFunc, - }, -}; - -const repoUri = 'github.com/elastic/TypeScript-Node-Starter'; - -const serverOptions = createTestServerOption(); -const server = createTestHapiServer(); -const gitOps = new GitOperations(serverOptions.repoPath); - -function cleanWorkspace() { - return new Promise(resolve => { - rimraf(serverOptions.workspacePath, resolve); - }); -} - -function setupEsClientSpy() { - // Mock a git status of the repo indicating the the repo is fully cloned already. - const getSpy = sinon.fake.returns( - Promise.resolve({ - _source: { - [RepositoryGitStatusReservedField]: { - uri: repoUri, - progress: WorkerReservedProgress.COMPLETED, - timestamp: new Date(), - cloneProgress: { - isCloned: true, - }, - }, - }, - }) - ); - const existsAliasSpy = sinon.fake.returns(false); - const createSpy = sinon.spy(); - const putAliasSpy = sinon.spy(); - const deleteByQuerySpy = sinon.spy(); - const bulkSpy = sinon.spy(); - esClient.bulk = bulkSpy; - esClient.indices.existsAlias = existsAliasSpy; - esClient.indices.create = createSpy; - esClient.indices.putAlias = putAliasSpy; - esClient.get = getSpy; - esClient.deleteByQuery = deleteByQuerySpy; - return { - getSpy, - existsAliasSpy, - createSpy, - putAliasSpy, - deleteByQuerySpy, - bulkSpy, - }; -} - -function setupLsServiceSendRequestSpy(): sinon.SinonSpy { - return sinon.fake.returns( - Promise.resolve({ - result: [ - { - // 1 mock symbol for each file - symbols: [ - { - symbolInformation: { - name: 'mocksymbolname', - }, - }, - ], - // 1 mock reference for each file - references: [{}], - }, - ], - }) - ); -} - -describe('LSP indexer unit tests', function(this: any) { - this.timeout(20000); - - // @ts-ignore - before(async () => { - return new Promise(resolve => { - rimraf(serverOptions.repoPath, resolve); - }); - }); - - beforeEach(async function() { - // @ts-ignore - this.timeout(200000); - return await prepareProject( - `https://${repoUri}.git`, - path.join(serverOptions.repoPath, repoUri) - ); - }); - // @ts-ignore - after(() => { - return cleanWorkspace(); - }); - - afterEach(() => { - sinon.restore(); - }); - - it('Normal LSP index process.', async () => { - // Setup the esClient spies - const { - existsAliasSpy, - createSpy, - putAliasSpy, - deleteByQuerySpy, - bulkSpy, - } = setupEsClientSpy(); - - const lspservice = new LspService( - '127.0.0.1', - serverOptions, - gitOps, - esClient as EsClient, - new InstallManager(server, serverOptions), - new ConsoleLoggerFactory(), - new RepositoryConfigController(esClient as EsClient) - ); - - const lspSendRequestSpy = setupLsServiceSendRequestSpy(); - lspservice.sendRequest = lspSendRequestSpy; - const supportLanguageSpy = sinon.stub(); - - // Setup supported languages, so that unsupported source files won't be - // sent for lsp requests. - supportLanguageSpy.withArgs('javascript').returns(true); - supportLanguageSpy.withArgs('typescript').returns(true); - lspservice.supportLanguage = supportLanguageSpy; - - const indexer = new LspIndexer( - repoUri, - 'master', - lspservice, - serverOptions, - gitOps, - esClient as EsClient, - log - ); - await indexer.start(); - - // Expect EsClient deleteByQuery called 3 times for repository cleaning before - // the index for document, symbol and reference, respectively. - assert.strictEqual(deleteByQuerySpy.callCount, 3); - - // Ditto for index and alias creation - assert.strictEqual(existsAliasSpy.callCount, 3); - assert.strictEqual(createSpy.callCount, 3); - assert.strictEqual(putAliasSpy.callCount, 3); - - // There are 22 files which are written in supported languages in the repo. 1 file + 1 symbol + 1 reference = 3 objects to - // index for each file. Total doc indexed for these files should be 3 * 22 = 66. - // The rest 158 files will only be indexed for document. - // There are also 10 binary files to be excluded. - // So the total number of index requests will be 66 + 158 - 10 = 214. - assert.strictEqual(bulkSpy.callCount, 5); - assert.strictEqual(lspSendRequestSpy.callCount, 22); - let total = 0; - for (let i = 0; i < bulkSpy.callCount; i++) { - total += bulkSpy.getCall(i).args[0].body.length; - } - assert.strictEqual(total, 214 * 2); - // @ts-ignore - }).timeout(20000); - - it('Cancel LSP index process.', async () => { - // Setup the esClient spies - const { - existsAliasSpy, - createSpy, - putAliasSpy, - deleteByQuerySpy, - bulkSpy, - } = setupEsClientSpy(); - - const lspservice = new LspService( - '127.0.0.1', - serverOptions, - gitOps, - esClient as EsClient, - new InstallManager(server, serverOptions), - new ConsoleLoggerFactory(), - new RepositoryConfigController(esClient as EsClient) - ); - - lspservice.sendRequest = setupLsServiceSendRequestSpy(); - - const indexer = new LspIndexer( - repoUri, - 'master', - lspservice, - serverOptions, - gitOps, - esClient as EsClient, - log - ); - // Cancel the indexer before start. - indexer.cancel(); - await indexer.start(); - - // Expect EsClient deleteByQuery called 3 times for repository cleaning before - // the index for document, symbol and reference, respectively. - assert.strictEqual(deleteByQuerySpy.callCount, 3); - - // Ditto for index and alias creation - assert.strictEqual(existsAliasSpy.callCount, 3); - assert.strictEqual(createSpy.callCount, 3); - assert.strictEqual(putAliasSpy.callCount, 3); - - // Because the indexer is cancelled already in the begining. 0 doc should be - // indexed and thus bulk won't be called. - assert.ok(bulkSpy.notCalled); - }); - - it('Index continues from a checkpoint', async () => { - // Setup the esClient spies - const { - existsAliasSpy, - createSpy, - putAliasSpy, - deleteByQuerySpy, - bulkSpy, - } = setupEsClientSpy(); - - const lspservice = new LspService( - '127.0.0.1', - serverOptions, - gitOps, - esClient as EsClient, - new InstallManager(server, serverOptions), - new ConsoleLoggerFactory(), - new RepositoryConfigController(esClient as EsClient) - ); - - const lspSendRequestSpy = setupLsServiceSendRequestSpy(); - lspservice.sendRequest = lspSendRequestSpy; - const supportLanguageSpy = sinon.stub(); - - // Setup supported languages, so that unsupported source files won't be - // sent for lsp requests. - supportLanguageSpy.withArgs('javascript').returns(true); - supportLanguageSpy.withArgs('typescript').returns(true); - lspservice.supportLanguage = supportLanguageSpy; - - const indexer = new LspIndexer( - repoUri, - 'HEAD', - lspservice, - serverOptions, - gitOps, - esClient as EsClient, - log - ); - - // Apply a checkpoint in here. - await indexer.start(undefined, { - repoUri: '', - filePath: 'src/public/js/main.ts', - revision: 'HEAD', - }); - - // Expect EsClient deleteByQuery called 0 times for repository cleaning while - // dealing with repository checkpoint. - assert.strictEqual(deleteByQuerySpy.callCount, 0); - - // Ditto for index and alias creation - assert.strictEqual(existsAliasSpy.callCount, 0); - assert.strictEqual(createSpy.callCount, 0); - assert.strictEqual(putAliasSpy.callCount, 0); - - // There are 22 files with supported language in the repo, but only 11 - // files after the checkpoint. 1 file + 1 symbol + 1 reference = 3 objects - // to index for each file. Total doc indexed for these files should be - // 3 * 11 = 33. Also there are 15 files without supported language. Only one - // document will be index for these files. So total index requests would be - // 33 + 15 = 48. - assert.strictEqual(bulkSpy.callCount, 2); - assert.strictEqual(lspSendRequestSpy.callCount, 11); - let total = 0; - for (let i = 0; i < bulkSpy.callCount; i++) { - total += bulkSpy.getCall(i).args[0].body.length; - } - assert.strictEqual(total, 48 * 2); - // @ts-ignore - }).timeout(20000); -}); diff --git a/x-pack/legacy/plugins/code/server/__tests__/lsp_service.ts b/x-pack/legacy/plugins/code/server/__tests__/lsp_service.ts deleted file mode 100644 index 6480402db9827..0000000000000 --- a/x-pack/legacy/plugins/code/server/__tests__/lsp_service.ts +++ /dev/null @@ -1,252 +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 fs from 'fs'; -import path from 'path'; -import sinon from 'sinon'; - -import assert from 'assert'; - -import { simplegit } from '@elastic/simple-git/dist'; -import { GitOperations } from '../git_operations'; -import { RepositoryConfigReservedField, RepositoryGitStatusReservedField } from '../indexer/schema'; -import { InstallManager } from '../lsp/install_manager'; -import { LspService } from '../lsp/lsp_service'; -import { RepositoryConfigController } from '../repository_config_controller'; -import { - createTestHapiServer, - createTestServerOption, - prepareProjectByCloning, - prepareProjectByInit, -} from '../test_utils'; -import { ConsoleLoggerFactory } from '../utils/console_logger_factory'; - -const filename = 'hello.ts'; -const packagejson = ` -{ - "name": "master", - "version": "1.0.0", - "description": "", - "main": "index.js", - "dependencies": {}, - "devDependencies": { - "typescript": "~3.3.3333" - }, - "scripts": { - "test": "echo \\"Error: no test specified\\" && exit 1" - }, - "author": "", - "license": "ISC" -} -`; -describe('lsp_service tests', () => { - let firstCommitSha = ''; - let secondCommitSha = ''; - async function prepareProject(repoPath: string) { - const { commits } = await prepareProjectByInit(repoPath, { - 'commit for test': { - [filename]: "console.log('hello world');", - }, - 'commit2 for test': { - 'package.json': packagejson, - }, - }); - - firstCommitSha = commits[0]; - secondCommitSha = commits[1]; - } - - const serverOptions = createTestServerOption(); - const server = createTestHapiServer(); - const installManager = new InstallManager(server, serverOptions); - const gitOps = new GitOperations(serverOptions.repoPath); - - function mockEsClient(): any { - const api = { - get(params: any) { - return { - _source: { - [RepositoryGitStatusReservedField]: { - cloneProgress: { - isCloned: true, - }, - }, - [RepositoryConfigReservedField]: { - disableTypescript: false, - }, - }, - }; - }, - }; - return api; - } - - const repoUri = 'github.com/test/test_repo'; - const mockRndPath = '__random'; - - // @ts-ignore - before(async () => { - const tmpRepo = path.join(serverOptions.repoPath, 'tmp'); - await prepareProject(tmpRepo); - await prepareProjectByCloning(`file://${tmpRepo}`, path.join(serverOptions.repoPath, repoUri)); - }); - - function comparePath(pathA: string, pathB: string) { - const pa = fs.realpathSync(pathA); - const pb = fs.realpathSync(pathB); - return path.resolve(pa) === path.resolve(pb); - } - - function mockLspService() { - const esClient = mockEsClient(); - const service = new LspService( - '127.0.0.1', - serverOptions, - gitOps, - esClient, - installManager, - new ConsoleLoggerFactory(), - new RepositoryConfigController(esClient) - ); - // @ts-ignore - service.workspaceHandler.randomPath = () => 'random'; - return service; - } - - async function sendHoverRequest(lspservice: LspService, revision: string) { - const method = 'textDocument/hover'; - - const params = { - textDocument: { - uri: `git://${repoUri}/blob/${revision}/${filename}`, - }, - position: { - line: 0, - character: 1, - }, - }; - return await lspservice.sendRequest(method, params); - } - - it('process a hover request', async () => { - const lspservice = mockLspService(); - try { - const workspaceHandler = lspservice.workspaceHandler; - const wsSpy = sinon.spy(workspaceHandler, 'handleRequest'); - const controller = lspservice.controller; - const ctrlSpy = sinon.spy(controller, 'handleRequest'); - - const revision = 'master'; - - const response = await sendHoverRequest(lspservice, revision); - assert.ok(response); - assert.ok(response.result.contents); - - wsSpy.restore(); - ctrlSpy.restore(); - - const workspaceFolderExists = fs.existsSync( - path.join(serverOptions.workspacePath, repoUri, mockRndPath, revision) - ); - // workspace is opened - assert.ok(workspaceFolderExists); - - const workspacePath = fs.realpathSync( - path.resolve(serverOptions.workspacePath, repoUri, mockRndPath, revision) - ); - // workspace handler is working, filled workspacePath - sinon.assert.calledWith( - ctrlSpy, - sinon.match.has('workspacePath', sinon.match(value => comparePath(value, workspacePath))) - ); - // uri is changed by workspace handler - sinon.assert.calledWith( - ctrlSpy, - sinon.match.hasNested('params.textDocument.uri', `file://${workspacePath}/${filename}`) - ); - return; - } finally { - await lspservice.shutdown(); - } - // @ts-ignore - }).timeout(30000); - - it('unload a workspace', async () => { - const lspservice = mockLspService(); - try { - const revision = 'master'; - // send a dummy request to open a workspace; - const response = await sendHoverRequest(lspservice, revision); - assert.ok(response); - const workspacePath = path.resolve( - serverOptions.workspacePath, - repoUri, - mockRndPath, - revision - ); - const workspaceFolderExists = fs.existsSync(workspacePath); - // workspace is opened - assert.ok(workspaceFolderExists); - const controller = lspservice.controller; - // @ts-ignore - const languageServer = controller.languageServerMap.typescript[0]; - const realWorkspacePath = fs.realpathSync(workspacePath); - - // @ts-ignore - const handler = await languageServer.languageServerHandlers[realWorkspacePath]; - const exitSpy = sinon.spy(handler, 'exit'); - const unloadSpy = sinon.spy(handler, 'unloadWorkspace'); - - await lspservice.deleteWorkspace(repoUri); - - unloadSpy.restore(); - exitSpy.restore(); - - sinon.assert.calledWith(unloadSpy, realWorkspacePath); - // typescript language server for this workspace should be closed - sinon.assert.calledOnce(exitSpy); - // the workspace folder should be deleted - const exists = fs.existsSync(realWorkspacePath); - assert.strictEqual(exists, false); - return; - } finally { - await lspservice.shutdown(); - } - // @ts-ignore - }).timeout(30000); - - it('should update if a worktree is not the newest', async () => { - const lspservice = mockLspService(); - try { - const revision = 'master'; - // send a dummy request to open a workspace; - const response = await sendHoverRequest(lspservice, revision); - assert.ok(response); - const workspacePath = path.resolve( - serverOptions.workspacePath, - repoUri, - mockRndPath, - revision - ); - const git = simplegit(workspacePath); - const workspaceCommit = await git.revparse(['HEAD']); - // workspace is newest now - assert.strictEqual(workspaceCommit, secondCommitSha); - // reset workspace to an older one - await git.reset([firstCommitSha, '--hard']); - assert.strictEqual(await git.revparse(['HEAD']), firstCommitSha); - - // send a request again; - await sendHoverRequest(lspservice, revision); - // workspace_handler should update workspace to the newest one - assert.strictEqual(await git.revparse(['HEAD']), secondCommitSha); - return; - } finally { - await lspservice.shutdown(); - } - // @ts-ignore - }).timeout(30000); -}); diff --git a/x-pack/legacy/plugins/code/server/__tests__/multi_node.ts b/x-pack/legacy/plugins/code/server/__tests__/multi_node.ts deleted file mode 100644 index 0b4a9c576eb4f..0000000000000 --- a/x-pack/legacy/plugins/code/server/__tests__/multi_node.ts +++ /dev/null @@ -1,166 +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 getPort from 'get-port'; -import { resolve } from 'path'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { Root } from 'src/core/server/root'; - -import { - createRootWithCorePlugins, - request, - createTestServers, -} from '../../../../../../src/test_utils/kbn_server'; - -const xpackOption = { - upgrade_assistant: { - enabled: false, - }, - security: { - enabled: false, - }, - ccr: { - enabled: false, - }, - monitoring: { - enabled: false, - }, - beats: { - enabled: false, - }, - ilm: { - enabled: false, - }, - logstash: { - enabled: false, - }, - rollup: { - enabled: false, - }, - watcher: { - enabled: false, - }, - remote_clusters: { - enabled: false, - }, - reporting: { - enabled: false, - }, - task_manager: { - enabled: false, - }, - maps: { - enabled: false, - }, - oss_telemetry: { - enabled: false, - }, - xpack_main: { - enabled: true, - }, -}; - -// FLAKY: https://github.com/elastic/kibana/issues/43960 -describe.skip('code in multiple nodes', () => { - const codeNodeUuid = 'c4add484-0cba-4e05-86fe-4baa112d9e53'; - const nonodeNodeUuid = '22b75e04-0e50-4647-9643-6b1b1d88beaf'; - let codePort: number; - let nonCodePort: number; - let nonCodeNode: Root; - - let kbnRootServer: any; - let kbn: any; - let esServer: any; - const pluginPaths = resolve(__dirname, '../../../../../'); - - async function startServers() { - const servers = createTestServers({ - adjustTimeout: t => { - // @ts-ignore - this.timeout(t); - }, - settings: { - kbn: { - server: { - uuid: codeNodeUuid, - port: codePort, - }, - plugins: { paths: [pluginPaths] }, - xpack: { ...xpackOption, code: { codeNodeUrl: `http://localhost:${codePort}` } }, - logging: { silent: false }, - }, - }, - }); - - esServer = await servers.startES(); - kbn = await servers.startKibana(); - kbnRootServer = kbn.root; - } - - async function startNonCodeNodeKibana() { - const setting = { - server: { - port: nonCodePort, - uuid: nonodeNodeUuid, - }, - plugins: { paths: [pluginPaths] }, - xpack: { - ...xpackOption, - code: { codeNodeUrl: `http://localhost:${codePort}` }, - }, - logging: { silent: true }, - }; - nonCodeNode = createRootWithCorePlugins(setting); - await nonCodeNode.setup(); - await nonCodeNode.start(); - } - - // @ts-ignore - before(async function() { - nonCodePort = await getPort(); - codePort = await getPort(); - - // @ts-ignore - await startServers.bind(this)(); - await startNonCodeNodeKibana(); - }); - - // @ts-ignore - after(async function() { - await nonCodeNode.shutdown(); - await kbn.stop(); - await esServer.stop(); - }); - - function delay(ms: number) { - return new Promise(resolve1 => { - setTimeout(resolve1, ms); - }); - } - - it('Code node setup should be ok', async () => { - await delay(6000); - await request.get(kbnRootServer, '/api/code/setup').expect(200); - // @ts-ignore - }).timeout(20000); - - it('Non-code node setup should be ok', async () => { - await delay(1000); - await request.get(nonCodeNode, '/api/code/setup').expect(200); - // @ts-ignore - }).timeout(5000); - - it('Non-code node setup should fail if code node is shutdown', async () => { - await kbn.stop(); - await request.get(nonCodeNode, '/api/code/setup').expect(502); - - // TODO restart root clearly is hard to do during platform migration - // A clear way is to createEsCluster individually and not reuse the - // same root - - // @ts-ignore - }).timeout(20000); -}); diff --git a/x-pack/legacy/plugins/code/server/__tests__/repository_service.ts b/x-pack/legacy/plugins/code/server/__tests__/repository_service.ts deleted file mode 100644 index b28b9650e2adf..0000000000000 --- a/x-pack/legacy/plugins/code/server/__tests__/repository_service.ts +++ /dev/null @@ -1,60 +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 assert from 'assert'; -// import { generateKeyPairSync } from 'crypto'; -import fs from 'fs'; -import * as os from 'os'; -import path from 'path'; -import rimraf from 'rimraf'; -import { RepositoryUtils } from '../../common/repository_utils'; -import { RepositoryService } from '../repository_service'; -import { ConsoleLogger } from '../utils/console_logger'; - -describe('repository service test', () => { - const log = new ConsoleLogger(); - const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'code_test')); - const repoDir = path.join(baseDir, 'repo'); - const credsDir = path.join(baseDir, 'credentials'); - // @ts-ignore - before(() => { - fs.mkdirSync(credsDir); - fs.mkdirSync(repoDir); - }); - // @ts-ignore - after(() => { - return rimraf.sync(baseDir); - }); - const service = new RepositoryService(repoDir, credsDir, log); - - it('can not clone a repo by ssh without a key', async () => { - const repo = RepositoryUtils.buildRepository( - 'git@github.com:elastic/TypeScript-Node-Starter.git' - ); - await assert.rejects(service.clone(repo)); - // @ts-ignore - }).timeout(60000); - - /* it('can clone a repo by ssh with a key', async () => { - - const repo = RepositoryUtils.buildRepository('git@github.com:elastic/code.git'); - const { publicKey, privateKey } = generateKeyPairSync('rsa', { - modulusLength: 4096, - publicKeyEncoding: { - type: 'pkcs1', - format: 'pem', - }, - privateKeyEncoding: { - type: 'pkcs1', - format: 'pem', - }, - }); - fs.writeFileSync(path.join(credsDir, 'id_rsa.pub'), publicKey); - fs.writeFileSync(path.join(credsDir, 'id_rsa'), privateKey); - const result = await service.clone(repo); - assert.ok(fs.existsSync(path.join(repoDir, result.repo.uri))); - }).timeout(60000); */ -}); diff --git a/x-pack/legacy/plugins/code/server/__tests__/workspace_handler.ts b/x-pack/legacy/plugins/code/server/__tests__/workspace_handler.ts deleted file mode 100644 index 0f1245b135076..0000000000000 --- a/x-pack/legacy/plugins/code/server/__tests__/workspace_handler.ts +++ /dev/null @@ -1,168 +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 fs from 'fs'; -import path from 'path'; - -import assert from 'assert'; -import * as os from 'os'; -import rimraf from 'rimraf'; -import { ResponseMessage } from 'vscode-jsonrpc/lib/messages'; - -import { LspRequest } from '../../model'; -import { GitOperations } from '../git_operations'; -import { WorkspaceHandler } from '../lsp/workspace_handler'; -import { ConsoleLoggerFactory } from '../utils/console_logger_factory'; -import { prepareProjectByInit } from '../test_utils'; - -const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'code_test')); -const workspaceDir = path.join(baseDir, 'workspace'); -const repoDir = path.join(baseDir, 'repo'); -const gitOps = new GitOperations(repoDir); - -describe('workspace_handler tests', () => { - function handleResponseUri(wh: WorkspaceHandler, uri: string) { - const dummyRequest: LspRequest = { - method: 'textDocument/edefinition', - params: [], - }; - const dummyResponse: ResponseMessage = { - id: null, - jsonrpc: '', - result: [ - { - location: { - uri, - }, - }, - ], - }; - wh.handleResponse(dummyRequest, dummyResponse); - return dummyResponse.result[0].location.uri; - } - - function makeAFile( - workspacePath: string = workspaceDir, - repo = 'github.com/Microsoft/TypeScript-Node-Starter', - revision = 'master', - file = 'src/controllers/user.ts' - ) { - const fullPath = path.join(workspacePath, repo, '__randomString', revision, file); - fs.mkdirSync(path.dirname(fullPath), { recursive: true }); - fs.writeFileSync(fullPath, ''); - const strInUrl = fullPath - .split(path.sep) - .map(value => encodeURIComponent(value)) - .join('/'); - const uri = `file:///${strInUrl}`; - return { repo, revision, file, uri }; - } - - it('file system url should be converted', async () => { - const workspaceHandler = new WorkspaceHandler( - gitOps, - workspaceDir, - // @ts-ignore - null, - new ConsoleLoggerFactory() - ); - const { repo, revision, file, uri } = makeAFile(workspaceDir); - const converted = handleResponseUri(workspaceHandler, uri); - assert.strictEqual(converted, `git://${repo}/blob/${revision}/${file}`); - }); - - it('should support symbol link', async () => { - const symlinkToWorkspace = path.join(baseDir, 'linkWorkspace'); - fs.symlinkSync(workspaceDir, symlinkToWorkspace, 'dir'); - // @ts-ignore - const workspaceHandler = new WorkspaceHandler( - gitOps, - symlinkToWorkspace, - // @ts-ignore - null, - new ConsoleLoggerFactory() - ); - - const { repo, revision, file, uri } = makeAFile(workspaceDir); - const converted = handleResponseUri(workspaceHandler, uri); - assert.strictEqual(converted, `git://${repo}/blob/${revision}/${file}`); - }); - - it('should support spaces in workspace dir', async () => { - const workspaceHasSpaces = path.join(baseDir, 'work space'); - const workspaceHandler = new WorkspaceHandler( - gitOps, - workspaceHasSpaces, - // @ts-ignore - null, - new ConsoleLoggerFactory() - ); - const { repo, revision, file, uri } = makeAFile(workspaceHasSpaces); - const converted = handleResponseUri(workspaceHandler, uri); - assert.strictEqual(converted, `git://${repo}/blob/${revision}/${file}`); - }); - - it('should throw a error if url is invalid', async () => { - const workspaceHandler = new WorkspaceHandler( - gitOps, - workspaceDir, - // @ts-ignore - null, - new ConsoleLoggerFactory() - ); - const invalidDir = path.join(baseDir, 'invalid_dir'); - const { uri } = makeAFile(invalidDir); - assert.throws(() => handleResponseUri(workspaceHandler, uri)); - }); - - async function prepareProject(repoPath: string) { - fs.mkdirSync(repoPath, { recursive: true }); - await prepareProjectByInit(repoPath, { - 'commit for test': { - 'src/app.ts': 'console.log("test")', - }, - }); - } - - it('should throw a error if file path is external', async () => { - const workspaceHandler = new WorkspaceHandler( - gitOps, - workspaceDir, - // @ts-ignore - null, - new ConsoleLoggerFactory() - ); - const repoUri = 'github.com/microsoft/typescript-node-starter'; - await prepareProject(path.join(repoDir, repoUri)); - const externalFile = 'node_modules/abbrev/abbrev.js'; - const request: LspRequest = { - method: 'textDocument/hover', - params: { - position: { - line: 8, - character: 23, - }, - textDocument: { - uri: `git://${repoUri}/blob/master/${externalFile}`, - }, - }, - }; - assert.rejects( - workspaceHandler.handleRequest(request), - new Error('invalid file path in requests.') - ); - }); - - // @ts-ignore - before(() => { - fs.mkdirSync(workspaceDir, { recursive: true }); - fs.mkdirSync(repoDir, { recursive: true }); - }); - - // @ts-ignore - after(() => { - rimraf.sync(baseDir); - }); -}); diff --git a/x-pack/legacy/plugins/code/server/disk_watermark.test.ts b/x-pack/legacy/plugins/code/server/disk_watermark.test.ts deleted file mode 100644 index f3f5040250c1f..0000000000000 --- a/x-pack/legacy/plugins/code/server/disk_watermark.test.ts +++ /dev/null @@ -1,193 +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 sinon from 'sinon'; - -import { DiskWatermarkService } from './disk_watermark'; -import { Logger } from './log'; -import { ServerOptions } from './server_options'; -import { ConsoleLoggerFactory } from './utils/console_logger_factory'; - -const log: Logger = new ConsoleLoggerFactory().getLogger(['test']); - -afterEach(() => { - sinon.restore(); -}); - -test('Disk watermark check in percentage mode', async () => { - const diskCheckerStub = sinon.stub(); - diskCheckerStub - .onFirstCall() - .resolves({ - free: 5, - size: 10, - }) - .onSecondCall() - .returns({ - free: 1, - size: 10, - }); - const diskWatermarkService = new DiskWatermarkService( - diskCheckerStub, - { - disk: { - thresholdEnabled: true, - watermarkLow: '80%', - }, - repoPath: '/', - } as ServerOptions, - log - ); - - expect(await diskWatermarkService.isLowWatermark()).toBeFalsy(); // 50% usage - expect(await diskWatermarkService.isLowWatermark()).toBeTruthy(); // 90% usage -}); - -test('Disk watermark check in absolute mode in kb', async () => { - const diskCheckerStub = sinon.stub(); - diskCheckerStub - .onFirstCall() - .resolves({ - free: 8 * Math.pow(1024, 1), - size: 10000, - }) - .onSecondCall() - .returns({ - free: 2 * Math.pow(1024, 1), - size: 10000, - }); - const diskWatermarkService = new DiskWatermarkService( - diskCheckerStub, - { - disk: { - thresholdEnabled: true, - watermarkLow: '4kb', - }, - repoPath: '/', - } as ServerOptions, - log - ); - - expect(await diskWatermarkService.isLowWatermark()).toBeFalsy(); // 8kb available - expect(await diskWatermarkService.isLowWatermark()).toBeTruthy(); // 2kb available -}); - -test('Disk watermark check in absolute mode in mb', async () => { - const diskCheckerStub = sinon.stub(); - diskCheckerStub - .onFirstCall() - .resolves({ - free: 8 * Math.pow(1024, 2), - size: 10000, - }) - .onSecondCall() - .returns({ - free: 2 * Math.pow(1024, 2), - size: 10000, - }); - const diskWatermarkService = new DiskWatermarkService( - diskCheckerStub, - { - disk: { - thresholdEnabled: true, - watermarkLow: '4mb', - }, - repoPath: '/', - } as ServerOptions, - log - ); - - expect(await diskWatermarkService.isLowWatermark()).toBeFalsy(); // 8mb available - expect(await diskWatermarkService.isLowWatermark()).toBeTruthy(); // 2mb available -}); - -test('Disk watermark check in absolute mode in gb', async () => { - const diskCheckerStub = sinon.stub(); - diskCheckerStub - .onFirstCall() - .resolves({ - free: 8 * Math.pow(1024, 3), - size: 10000, - }) - .onSecondCall() - .returns({ - free: 2 * Math.pow(1024, 3), - size: 10000, - }); - const diskWatermarkService = new DiskWatermarkService( - diskCheckerStub, - { - disk: { - thresholdEnabled: true, - watermarkLow: '4gb', - }, - repoPath: '/', - } as ServerOptions, - log - ); - - expect(await diskWatermarkService.isLowWatermark()).toBeFalsy(); // 8gb available - expect(await diskWatermarkService.isLowWatermark()).toBeTruthy(); // 2gb available -}); - -test('Disk watermark check in absolute mode in tb', async () => { - const diskCheckerStub = sinon.stub(); - diskCheckerStub - .onFirstCall() - .resolves({ - free: 8 * Math.pow(1024, 4), - size: 10000, - }) - .onSecondCall() - .returns({ - free: 2 * Math.pow(1024, 4), - size: 10000, - }); - const diskWatermarkService = new DiskWatermarkService( - diskCheckerStub, - { - disk: { - thresholdEnabled: true, - watermarkLow: '4tb', - }, - repoPath: '/', - } as ServerOptions, - log - ); - - expect(await diskWatermarkService.isLowWatermark()).toBeFalsy(); // 8tb available - expect(await diskWatermarkService.isLowWatermark()).toBeTruthy(); // 2tb available -}); - -test('Disk watermark check in invalid config', async () => { - const diskCheckerStub = sinon.stub(); - diskCheckerStub - .onFirstCall() - .resolves({ - free: 50, - size: 100, - }) - .onSecondCall() - .returns({ - free: 5, - size: 100, - }); - const diskWatermarkService = new DiskWatermarkService( - diskCheckerStub, - { - disk: { - thresholdEnabled: true, - // invalid config, will fallback with 90% by default - watermarkLow: '1234', - }, - repoPath: '/', - } as ServerOptions, - log - ); - - expect(await diskWatermarkService.isLowWatermark()).toBeFalsy(); // 50% usage - expect(await diskWatermarkService.isLowWatermark()).toBeTruthy(); // 95% usage -}); diff --git a/x-pack/legacy/plugins/code/server/disk_watermark.ts b/x-pack/legacy/plugins/code/server/disk_watermark.ts deleted file mode 100644 index 356737fe8e19a..0000000000000 --- a/x-pack/legacy/plugins/code/server/disk_watermark.ts +++ /dev/null @@ -1,106 +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 { i18n } from '@kbn/i18n'; -import { CheckDiskSpaceResult } from 'check-disk-space'; - -import { Logger } from './log'; -import { ServerOptions } from './server_options'; - -export const DEFAULT_WATERMARK_LOW_PERCENTAGE = 90; - -export type DiskCheckResult = CheckDiskSpaceResult; -export type DiskSpaceChecker = (path: string) => Promise; - -export class DiskWatermarkService { - // True for percentage mode (e.g. 90%), false for absolute mode (e.g. 500mb) - private percentageMode: boolean = true; - private watermark: number = DEFAULT_WATERMARK_LOW_PERCENTAGE; - private enabled: boolean = false; - - constructor( - private readonly diskSpaceChecker: DiskSpaceChecker, - private readonly serverOptions: ServerOptions, - private readonly logger: Logger - ) { - this.enabled = this.serverOptions.disk.thresholdEnabled; - if (this.enabled) { - this.parseWatermarkConfigString(this.serverOptions.disk.watermarkLow); - } - } - - public async isLowWatermark(): Promise { - if (!this.enabled) { - return false; - } - - try { - const res = await this.diskSpaceChecker(this.serverOptions.repoPath); - const { free, size } = res; - if (this.percentageMode) { - const percentage = ((size - free) * 100) / size; - return percentage > this.watermark; - } else { - return free <= this.watermark; - } - } catch (err) { - return true; - } - } - - public diskWatermarkViolationMessage(): string { - if (this.percentageMode) { - return i18n.translate('xpack.code.git.diskWatermarkLowPercentageMessage', { - defaultMessage: `Disk usage watermark level higher than {watermark}`, - values: { - watermark: this.serverOptions.disk.watermarkLow, - }, - }); - } else { - return i18n.translate('xpack.code.git.diskWatermarkLowMessage', { - defaultMessage: `Available disk space lower than {watermark}`, - values: { - watermark: this.serverOptions.disk.watermarkLow, - }, - }); - } - } - - private parseWatermarkConfigString(diskWatermarkLow: string) { - // Including undefined, null and empty string. - if (!diskWatermarkLow) { - this.logger.error( - `Empty disk watermark config for Code. Fallback with default value (${DEFAULT_WATERMARK_LOW_PERCENTAGE}%)` - ); - return; - } - - try { - const str = diskWatermarkLow.trim().toLowerCase(); - if (str.endsWith('%')) { - this.percentageMode = true; - this.watermark = parseInt(str.substr(0, str.length - 1), 10); - } else if (str.endsWith('kb')) { - this.percentageMode = false; - this.watermark = parseInt(str.substr(0, str.length - 2), 10) * Math.pow(1024, 1); - } else if (str.endsWith('mb')) { - this.percentageMode = false; - this.watermark = parseInt(str.substr(0, str.length - 2), 10) * Math.pow(1024, 2); - } else if (str.endsWith('gb')) { - this.percentageMode = false; - this.watermark = parseInt(str.substr(0, str.length - 2), 10) * Math.pow(1024, 3); - } else if (str.endsWith('tb')) { - this.percentageMode = false; - this.watermark = parseInt(str.substr(0, str.length - 2), 10) * Math.pow(1024, 4); - } else { - throw new Error('Unrecognized unit for disk size config.'); - } - } catch (error) { - this.logger.error( - `Invalid disk watermark config for Code. Fallback with default value (${DEFAULT_WATERMARK_LOW_PERCENTAGE}%)` - ); - } - } -} diff --git a/x-pack/legacy/plugins/code/server/distributed/apis/git_api.ts b/x-pack/legacy/plugins/code/server/distributed/apis/git_api.ts deleted file mode 100644 index a9a33dca3f96a..0000000000000 --- a/x-pack/legacy/plugins/code/server/distributed/apis/git_api.ts +++ /dev/null @@ -1,171 +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 fileType from 'file-type'; -import Boom from 'boom'; -import { GitOperations } from '../../git_operations'; -import { FileTree } from '../../../model'; -import { RequestContext, ServiceHandlerFor } from '../service_definition'; -import { extractLines } from '../../utils/buffer'; -import { detectLanguage } from '../../utils/detect_language'; -import { TEXT_FILE_LIMIT } from '../../../common/file'; -import { CommitInfo, ReferenceInfo } from '../../../model/commit'; -import { CommitDiff } from '../../../common/git_diff'; -import { GitBlame } from '../../../common/git_blame'; - -interface FileLocation { - uri: string; - path: string; - revision: string; -} -export const GitServiceDefinitionOption = { routePrefix: '/api/code/internal/git' }; -export const GitServiceDefinition = { - fileTree: { - request: {} as { - uri: string; - path: string; - revision: string; - skip: number; - limit: number; - withParents: boolean; - flatten: boolean; - }, - response: {} as FileTree, - }, - blob: { - request: {} as FileLocation & { line?: string }, - response: {} as { - isBinary: boolean; - imageType?: string; - content?: string; - lang?: string; - }, - }, - raw: { - request: {} as FileLocation, - response: {} as { - isBinary: boolean; - content: Buffer; - }, - }, - history: { - request: {} as FileLocation & { - count: number; - after: boolean; - }, - response: {} as CommitInfo[], - }, - branchesAndTags: { - request: {} as { uri: string }, - response: {} as ReferenceInfo[], - }, - commitDiff: { - request: {} as { uri: string; revision: string }, - response: {} as CommitDiff, - }, - blame: { - request: {} as FileLocation, - response: {} as GitBlame[], - }, - commit: { - request: {} as { uri: string; revision: string }, - response: {} as CommitInfo, - }, - headRevision: { - request: {} as { uri: string }, - response: {} as string, - }, -}; - -export const getGitServiceHandler = ( - gitOps: GitOperations -): ServiceHandlerFor => ({ - async fileTree( - { uri, path, revision, skip, limit, withParents, flatten }, - context: RequestContext - ) { - return await gitOps.fileTree(uri, path, revision, skip, limit, withParents, flatten); - }, - async blob({ uri, path, revision, line }) { - const blob = await gitOps.fileContent(uri, path, revision); - const isBinary = blob.isBinary(); - if (isBinary) { - const type = fileType(blob.content()); - if (type && type.mime && type.mime.startsWith('image/')) { - return { - isBinary, - imageType: type.mime, - content: blob.content().toString(), - }; - } else { - return { - isBinary, - }; - } - } else { - if (line) { - const [from, to] = line.split(','); - let fromLine = parseInt(from, 10); - let toLine = to === undefined ? fromLine + 1 : parseInt(to, 10); - if (fromLine > toLine) { - [fromLine, toLine] = [toLine, fromLine]; - } - const lines = extractLines(blob.content(), fromLine, toLine); - const lang = await detectLanguage(path, lines); - return { - isBinary, - lang, - content: lines, - }; - } else if (blob.rawsize() <= TEXT_FILE_LIMIT) { - const lang = await detectLanguage(path, blob.content()); - return { - isBinary, - lang, - content: blob.content().toString(), - }; - } else { - return { - isBinary, - }; - } - } - }, - async raw({ uri, path, revision }) { - const blob = await gitOps.fileContent(uri, path, revision); - const isBinary = blob.isBinary(); - return { - isBinary, - content: blob.content(), - }; - }, - async history({ uri, path, revision, count, after }) { - const commit = await gitOps.getCommitInfo(uri, revision); - if (commit === null) { - throw Boom.notFound(`commit ${revision} not found in repo ${uri}`); - } - let commits = await gitOps.log(uri, commit.id, after ? count + 1 : count, path); - if (after && commits.length > 0) { - commits = commits.slice(1); - } - return commits; - }, - async branchesAndTags({ uri }) { - return await gitOps.getBranchAndTags(uri); - }, - async commitDiff({ uri, revision }) { - return await gitOps.getCommitDiff(uri, revision); - }, - async blame({ uri, path, revision }) { - return await gitOps.blame(uri, revision, path); - }, - async commit({ uri, revision }) { - return await gitOps.getCommitOr404(uri, revision); - }, - async headRevision({ uri }) { - return await gitOps.getHeadRevision(uri); - }, -}); diff --git a/x-pack/legacy/plugins/code/server/distributed/apis/index.ts b/x-pack/legacy/plugins/code/server/distributed/apis/index.ts deleted file mode 100644 index 1a33cc7f220df..0000000000000 --- a/x-pack/legacy/plugins/code/server/distributed/apis/index.ts +++ /dev/null @@ -1,11 +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 * from './git_api'; -export * from './lsp_api'; -export * from './workspace_api'; -export * from './setup_api'; -export * from './repository_api'; diff --git a/x-pack/legacy/plugins/code/server/distributed/apis/lsp_api.ts b/x-pack/legacy/plugins/code/server/distributed/apis/lsp_api.ts deleted file mode 100644 index 222c7ee476098..0000000000000 --- a/x-pack/legacy/plugins/code/server/distributed/apis/lsp_api.ts +++ /dev/null @@ -1,49 +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 { ResponseMessage } from 'vscode-jsonrpc/lib/messages'; -import { LspService } from '../../lsp/lsp_service'; -import { ServiceHandlerFor } from '../service_definition'; -import { LanguageServerDefinition } from '../../lsp/language_servers'; -import { LanguageServerStatus } from '../../../common/language_server'; -import { WorkspaceStatus } from '../../lsp/request_expander'; - -export const LspServiceDefinitionOption = { routePrefix: '/api/code/internal/lsp' }; -export const LspServiceDefinition = { - sendRequest: { - request: {} as { method: string; params: any }, - response: {} as ResponseMessage, - }, - languageSeverDef: { - request: {} as { lang: string }, - response: {} as LanguageServerDefinition[], - }, - languageServerStatus: { - request: {} as { langName: string }, - response: {} as LanguageServerStatus, - }, - initializeState: { - request: {} as { repoUri: string; revision: string }, - response: {} as { [p: string]: WorkspaceStatus }, - }, -}; - -export const getLspServiceHandler = ( - lspService: LspService -): ServiceHandlerFor => ({ - async sendRequest({ method, params }) { - return await lspService.sendRequest(method, params); - }, - async languageSeverDef({ lang }) { - return lspService.getLanguageSeverDef(lang); - }, - async languageServerStatus({ langName }) { - return lspService.languageServerStatus(langName); - }, - async initializeState({ repoUri, revision }) { - return await lspService.initializeState(repoUri, revision); - }, -}); diff --git a/x-pack/legacy/plugins/code/server/distributed/apis/repository_api.ts b/x-pack/legacy/plugins/code/server/distributed/apis/repository_api.ts deleted file mode 100644 index 384ebc56acca2..0000000000000 --- a/x-pack/legacy/plugins/code/server/distributed/apis/repository_api.ts +++ /dev/null @@ -1,46 +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 { ServiceHandlerFor } from '../service_definition'; -import { CloneWorker, DeleteWorker, IndexWorker } from '../../queue'; - -export const RepositoryServiceDefinition = { - clone: { - request: {} as { url: string }, - response: {}, - }, - delete: { - request: {} as { uri: string }, - response: {}, - }, - index: { - request: {} as { - uri: string; - revision: string | undefined; - enforceReindex: boolean; - }, - response: {}, - }, -}; - -export const getRepositoryHandler = ( - cloneWorker: CloneWorker, - deleteWorker: DeleteWorker, - indexWorker: IndexWorker -): ServiceHandlerFor => ({ - async clone(payload: { url: string }) { - await cloneWorker.enqueueJob(payload, {}); - return {}; - }, - async delete(payload: { uri: string }) { - await deleteWorker.enqueueJob(payload, {}); - return {}; - }, - async index(payload: { uri: string; revision: string | undefined; enforceReindex: boolean }) { - await indexWorker.enqueueJob(payload, {}); - return {}; - }, -}); diff --git a/x-pack/legacy/plugins/code/server/distributed/apis/setup_api.ts b/x-pack/legacy/plugins/code/server/distributed/apis/setup_api.ts deleted file mode 100644 index 229470166568b..0000000000000 --- a/x-pack/legacy/plugins/code/server/distributed/apis/setup_api.ts +++ /dev/null @@ -1,20 +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 { ServiceHandlerFor } from '../service_definition'; - -export const SetupDefinition = { - setup: { - request: {}, - response: {} as string, - }, -}; - -export const setupServiceHandler: ServiceHandlerFor = { - async setup() { - return 'ok'; - }, -}; diff --git a/x-pack/legacy/plugins/code/server/distributed/apis/workspace_api.ts b/x-pack/legacy/plugins/code/server/distributed/apis/workspace_api.ts deleted file mode 100644 index e370efe0f37f6..0000000000000 --- a/x-pack/legacy/plugins/code/server/distributed/apis/workspace_api.ts +++ /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. - */ - -import { ServiceHandlerFor } from '../service_definition'; -import { WorkspaceHandler } from '../../lsp/workspace_handler'; -import { RepoConfig } from '../../../model'; -import { WorkspaceCommand } from '../../lsp/workspace_command'; -import { LoggerFactory as CodeLoggerFactory } from '../../utils/log_factory'; - -export const WorkspaceDefinition = { - initCmd: { - request: {} as { repoUri: string; revision: string; repoConfig: RepoConfig; force: boolean }, - response: {}, - }, -}; - -export const getWorkspaceHandler = ( - loggerFactory: CodeLoggerFactory, - workspaceHandler: WorkspaceHandler -): ServiceHandlerFor => ({ - async initCmd({ repoUri, revision, repoConfig, force }) { - try { - const { workspaceDir, workspaceRevision } = await workspaceHandler.openWorkspace( - repoUri, - revision - ); - - const workspaceCmd = new WorkspaceCommand( - repoConfig, - workspaceDir, - workspaceRevision, - loggerFactory.getLogger(['workspace', repoUri]) - ); - await workspaceCmd.runInit(force); - return {}; - } catch (e) { - if (e.isBoom) { - return e; - } - } - }, -}); diff --git a/x-pack/legacy/plugins/code/server/distributed/cluster/cluster_membership_service.ts b/x-pack/legacy/plugins/code/server/distributed/cluster/cluster_membership_service.ts deleted file mode 100644 index f2f1c82db0070..0000000000000 --- a/x-pack/legacy/plugins/code/server/distributed/cluster/cluster_membership_service.ts +++ /dev/null @@ -1,23 +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 { CodeNode } from './code_nodes'; - -/** - * The service is used to manage the membership of the node in the cluster. - * It may actively add or remove members of the cluster. - * - * Migration Plan: - * - */ -export interface ClusterMembershipService { - // the information of the local node - readonly localNode: CodeNode; - - start(): Promise; - - stop(): Promise; -} diff --git a/x-pack/legacy/plugins/code/server/distributed/cluster/cluster_meta.test.ts b/x-pack/legacy/plugins/code/server/distributed/cluster/cluster_meta.test.ts deleted file mode 100644 index 2ed052d9a838d..0000000000000 --- a/x-pack/legacy/plugins/code/server/distributed/cluster/cluster_meta.test.ts +++ /dev/null @@ -1,73 +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 { ClusterMetadata } from './cluster_meta'; -import { Repository } from '../../../model'; -import { RoutingTable } from './routing_table'; -import { ClusterState } from './cluster_state'; -import { CodeNodes } from './code_nodes'; - -const repoCmpFunc = (a: Repository, b: Repository): number => { - return a.url.localeCompare(b.url); -}; - -test('an empty routing table should be well defined', () => { - // test an empty table - const table = new RoutingTable([]); - expect(table.nodeIds().length).toBe(0); - expect(table.repositoryURIs().length).toBe(0); - expect(table.getRepositoryURIsByNodeId('test').length).toBe(0); -}); - -test('basic functions of a simple routing table', () => { - // test an empty table - const table = new RoutingTable([ - { nodeId: 'n1', resource: 'r1' }, - { nodeId: 'n1', resource: 'r2' }, - { nodeId: 'n2', resource: 'r3' }, - { nodeId: 'n3', resource: 'r4' }, - ]); - expect(table.nodeIds().length).toBe(3); - expect(table.repositoryURIs().length).toBe(4); - expect(table.getRepositoryURIsByNodeId('n1')).toEqual(['r1', 'r2']); - expect(table.getRepositoryURIsByNodeId('n2')).toEqual(['r3']); - expect(table.getRepositoryURIsByNodeId('n3')).toEqual(['r4']); - expect(table.getRepositoryURIsByNodeId('n4').length).toBe(0); -}); - -test('cluster state functions', () => { - const repositories: Repository[] = [ - { uri: 'github/a/a', url: 'http://github/a/a' }, - { uri: 'github/b/b', url: 'http://github/b/b' }, - { uri: 'github/c/c', url: 'http://github/c/c' }, - ]; - const state = new ClusterState( - new ClusterMetadata(repositories), - new RoutingTable([ - { nodeId: 'n1', resource: 'github/a/a' }, - { nodeId: 'n1', resource: 'github/b/b' }, - { nodeId: 'n2', resource: 'github/c/c' }, - // a non-exist repository - { nodeId: 'n2', resource: 'github/d/d' }, - ]), - new CodeNodes([]) - ); - - let repos = state.getNodeRepositories('n0'); - expect(repos.length).toBe(0); - - repos = state.getNodeRepositories('n1'); - expect(repos).toBeDefined(); - expect(repos!.length).toBe(2); - repos!.sort(repoCmpFunc); - expect(repos![0].uri).toBe('github/a/a'); - expect(repos![1].uri).toBe('github/b/b'); - - repos = state.getNodeRepositories('n2'); - expect(repos).toBeDefined(); - expect(repos!.length).toBe(1); - expect(repos![0].uri).toBe('github/c/c'); -}); diff --git a/x-pack/legacy/plugins/code/server/distributed/cluster/cluster_meta.ts b/x-pack/legacy/plugins/code/server/distributed/cluster/cluster_meta.ts deleted file mode 100644 index 5d92fc75a7b1c..0000000000000 --- a/x-pack/legacy/plugins/code/server/distributed/cluster/cluster_meta.ts +++ /dev/null @@ -1,26 +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 { Repository } from '../../../model'; - -/** - * A snapshot of the cluster meta data, which includes: - * - repository objects - */ -export class ClusterMetadata { - private readonly uri2Repo: Map; - - constructor(public readonly repositories: Repository[] = []) { - this.uri2Repo = repositories.reduce((prev, cur) => { - prev.set(cur.uri, cur); - return prev; - }, new Map()); - } - - public getRepository(uri: string): Repository | undefined { - return this.uri2Repo.get(uri); - } -} diff --git a/x-pack/legacy/plugins/code/server/distributed/cluster/cluster_node_adapter.ts b/x-pack/legacy/plugins/code/server/distributed/cluster/cluster_node_adapter.ts deleted file mode 100644 index 9d168e604c1b3..0000000000000 --- a/x-pack/legacy/plugins/code/server/distributed/cluster/cluster_node_adapter.ts +++ /dev/null @@ -1,185 +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 { KibanaRequest, KibanaResponseFactory, RequestHandlerContext } from 'src/core/server'; -import util from 'util'; -import Boom from 'boom'; -import { ServiceHandlerAdapter, ServiceRegisterOptions } from '../service_handler_adapter'; -import { ResourceLocator } from '../resource_locator'; -import { - MethodHandler, - MethodsFor, - ServiceDefinition, - ServiceHandlerFor, - ServiceMethod, - ServiceMethodMap, -} from '../service_definition'; -import { ServerOptions } from '../../server_options'; -import { ClusterMembershipService } from './cluster_membership_service'; -import { ConfigClusterMembershipService } from './config_cluster_membership_service'; -import { ClusterService } from './cluster_service'; -import { ClusterResourceLocator } from './cluster_resource_locator'; -import { CodeServerRouter } from '../../security'; -import { Logger } from '../../log'; -import { NonCodeNodeAdapter } from '../multinode/non_code_node_adapter'; -import { RequestPayload } from '../multinode/code_node_adapter'; -import { EsClient } from '../../lib/esqueue'; -import { ResourceSchedulerService } from './resource_scheduler_service'; -import { ClusterNodeEndpoint } from './cluster_node_endpoint'; - -/** - * Serve requests based on the routing table. - * - * For local request: - * - serve request locally or reroute to the remote node, based on the routing table - * - * For remote request: - * - serve request locally if the requested resource is on the local node, otherwise reject it - */ -export class ClusterNodeAdapter implements ServiceHandlerAdapter { - readonly clusterService: ClusterService; - readonly clusterMembershipService: ClusterMembershipService; - private readonly schedulerService: ResourceSchedulerService; - private readonly handlers: Map = new Map(); - // used to forward requests - private readonly nonCodeAdapter: NonCodeNodeAdapter = new NonCodeNodeAdapter('', this.log); - - constructor( - private readonly router: CodeServerRouter, - private readonly log: Logger, - serverOptions: ServerOptions, - esClient: EsClient - ) { - this.clusterService = new ClusterService(esClient, log); - this.clusterMembershipService = new ConfigClusterMembershipService( - serverOptions, - this.clusterService, - log - ); - this.schedulerService = new ResourceSchedulerService(this.clusterService, log); - this.locator = new ClusterResourceLocator( - this.clusterService, - this.clusterMembershipService, - this.schedulerService - ); - } - - locator: ResourceLocator; - - async start(): Promise { - await this.clusterService.start(); - await this.clusterMembershipService.start(); - await this.schedulerService.start(); - } - - async stop(): Promise { - await this.schedulerService.stop(); - await this.clusterMembershipService.stop(); - await this.clusterService.stop(); - } - - getService(serviceDefinition: DEF): ServiceMethodMap { - const handler = this.handlers.get(serviceDefinition); - if (!handler) { - throw new Error(`Handler not exists for ${serviceDefinition}`); - } - return handler as ServiceMethodMap; - } - - registerHandler( - serviceDefinition: DEF, - serviceHandler: ServiceHandlerFor | null, - options: ServiceRegisterOptions - ): ServiceMethodMap { - if (!serviceHandler) { - throw new Error('Service handler cannot be null'); - } - const routableServiceHandler: ServiceMethodMap = {} as ServiceMethodMap; - - // register a local handler that is able to route the request - for (const method in serviceDefinition) { - if (serviceDefinition.hasOwnProperty(method)) { - const localHandler = serviceHandler[method]; - const wrappedHandler = this.wrapRoutableHandler( - method, - serviceDefinition[method], - localHandler, - options - ); - routableServiceHandler[method] = wrappedHandler; - - const d = serviceDefinition[method]; - const path = `${options.routePrefix}/${d.routePath || method}`; - this.router.route({ - method: 'post', - path, - npHandler: async ( - ctx: RequestHandlerContext, - req: KibanaRequest, - res: KibanaResponseFactory - ) => { - const { context, params } = req.body as RequestPayload; - this.log.debug(`Receiving RPC call ${req.url.path} ${util.inspect(params)}`); - try { - const data = await localHandler(params, context); - return res.ok({ body: { data } }); - } catch (e) { - if (Boom.isBoom(e)) { - throw e; - } else { - throw Boom.boomify(e, { statusCode: 500 }); - } - } - }, - }); - this.log.info(`Registered handler for ${path}`); - } - } - - this.handlers.set(serviceDefinition, routableServiceHandler); - - return routableServiceHandler; - } - - private wrapRoutableHandler< - SERVICE_DEFINITION extends ServiceDefinition, - METHOD extends MethodsFor - >( - method: string, - d: { routePath?: string }, - handler: MethodHandler, - options: ServiceRegisterOptions - ): ServiceMethod { - return async (endpoint, params) => { - const requestContext = endpoint.toContext(); - if (endpoint instanceof ClusterNodeEndpoint) { - let path = `${options.routePrefix}/${d.routePath || method}`; - // if we want to join base url http://localhost:5601/api with path /abc/def to get - // http://localhost:5601/api/abc/def, the base url must end with '/', and the path cannot start with '/' - // see https://github.com/hapijs/wreck/commit/6fc514c58c5c181327fa3e84d2a95d7d8b93f079 - if (path.startsWith('/')) { - path = path.substr(1); - } - const payload = { - context: requestContext, - params, - } as RequestPayload; - this.log.debug( - `Request [${path}] at [${endpoint.codeNode.address}] with [${util.inspect(payload)}]` - ); - const { data } = await this.nonCodeAdapter.requestFn( - endpoint.codeNode.address, - path, - payload, - endpoint.httpRequest - ); - return data; - } else { - return handler(params, requestContext); - } - }; - } -} diff --git a/x-pack/legacy/plugins/code/server/distributed/cluster/cluster_node_endpoint.ts b/x-pack/legacy/plugins/code/server/distributed/cluster/cluster_node_endpoint.ts deleted file mode 100644 index e23b5a9027e75..0000000000000 --- a/x-pack/legacy/plugins/code/server/distributed/cluster/cluster_node_endpoint.ts +++ /dev/null @@ -1,19 +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 { KibanaRequest } from 'src/core/server'; -import { LocalEndpoint } from '../local_endpoint'; -import { CodeNode } from './code_nodes'; - -export class ClusterNodeEndpoint extends LocalEndpoint { - constructor( - public readonly httpRequest: KibanaRequest, - public readonly resource: string, - public readonly codeNode: CodeNode - ) { - super(httpRequest, resource); - } -} diff --git a/x-pack/legacy/plugins/code/server/distributed/cluster/cluster_resource_locator.ts b/x-pack/legacy/plugins/code/server/distributed/cluster/cluster_resource_locator.ts deleted file mode 100644 index 6ac0b830905bb..0000000000000 --- a/x-pack/legacy/plugins/code/server/distributed/cluster/cluster_resource_locator.ts +++ /dev/null @@ -1,66 +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 { KibanaRequest } from 'src/core/server'; -import Boom from 'boom'; -import { Endpoint, ResourceLocator } from '../resource_locator'; -import { ClusterService } from './cluster_service'; -import { LocalEndpoint } from '../local_endpoint'; -import { RepositoryUtils } from '../../../common/repository_utils'; -import { ResourceSchedulerService } from './resource_scheduler_service'; -import { ClusterMembershipService } from './cluster_membership_service'; -import { ClusterNodeEndpoint } from './cluster_node_endpoint'; - -export class ClusterResourceLocator implements ResourceLocator { - constructor( - private readonly clusterService: ClusterService, - private readonly clusterMembershipService: ClusterMembershipService, - // @ts-ignore - private readonly schedulerService: ResourceSchedulerService - ) {} - - protected repositoryUri(url: string): string { - return RepositoryUtils.buildRepository(url).uri; - } - - async locate(req: KibanaRequest, resource: string): Promise { - // to be compatible with - if (resource.trim() === '') { - return new LocalEndpoint(req, resource); - } - const state = this.clusterService.state(); - const nodeId = state.routingTable.getNodeIdByRepositoryURI(this.repositoryUri(resource)); - if (!nodeId) { - throw Boom.notFound(`resource [${resource}] not exists`); - } - if (this.clusterMembershipService.localNode.id === nodeId) { - return new LocalEndpoint(req, resource); - } else { - const node = state.nodes.getNodeById(nodeId); - if (!node) { - throw Boom.notFound(`Node [${nodeId}] not found`); - } - return new ClusterNodeEndpoint(req, resource, node); - } - } - - async isResourceLocal(resource: string): Promise { - const state = this.clusterService.state(); - return ( - this.clusterMembershipService.localNode.id === - state.routingTable.getNodeIdByRepositoryURI(this.repositoryUri(resource)) - ); - } - - /** - * Return undefined to let NodeRepositoriesService enqueue the clone job in cluster mode. - */ - async allocate(req: KibanaRequest, resource: string): Promise { - // make the cluster service synchronize the meta data and allocate new resources to nodes - await this.clusterService.pollClusterState(); - return undefined; - } -} diff --git a/x-pack/legacy/plugins/code/server/distributed/cluster/cluster_service.test.ts b/x-pack/legacy/plugins/code/server/distributed/cluster/cluster_service.test.ts deleted file mode 100644 index 38e6a0b5dda31..0000000000000 --- a/x-pack/legacy/plugins/code/server/distributed/cluster/cluster_service.test.ts +++ /dev/null @@ -1,71 +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 { ClusterService } from './cluster_service'; -import { Logger } from '../../log'; -import { ConsoleLoggerFactory } from '../../utils/console_logger_factory'; -import { EsClient } from '../../lib/esqueue'; -import { RepositoryObjectClient } from '../../search'; -import { Repository } from '../../../model'; -import sinon from 'sinon'; -import { ResourceSchedulerService } from './resource_scheduler_service'; -import { ClusterState } from './cluster_state'; -import { ClusterMetadata } from './cluster_meta'; -import { RoutingTable } from './routing_table'; -import { CodeNodes } from './code_nodes'; - -const log: Logger = new ConsoleLoggerFactory().getLogger(['test']); - -afterEach(() => { - sinon.restore(); -}); - -test('test poll cluster state', async () => { - const esClient = {} as EsClient; - const repoClient = {} as RepositoryObjectClient; - repoClient.getAllRepositories = sinon.fake.returns([ - { - uri: 'github/a/a', - url: 'http://github/a/a', - } as Repository, - ]); - const clusterService = new ClusterService(esClient, log, repoClient); - clusterService.setClusterState( - new ClusterState( - new ClusterMetadata(), - new RoutingTable(), - new CodeNodes([{ id: 'test', address: 'localhost' }]) - ) - ); - - let state = clusterService.state(); - expect(state).toBeDefined(); - expect(state.clusterMeta.repositories.length).toBe(0); - expect(state.routingTable.nodeIds().length).toBe(0); - expect(state.routingTable.repositoryURIs().length).toBe(0); - - await clusterService.pollClusterState(); - const schedulerService: ResourceSchedulerService = new ResourceSchedulerService( - clusterService, - log - ); - await schedulerService.allocateUnassigned(); - - state = clusterService.state(); - expect(state).toBeDefined(); - expect(state.clusterMeta.repositories.length).toBe(1); - const repo = state.clusterMeta.getRepository('github/a/a'); - expect(repo).toBeDefined(); - expect(repo!.url).toBe('http://github/a/a'); - expect(state.routingTable.getRepositoryURIsByNodeId('test')).toEqual(['github/a/a']); - - repoClient.getAllRepositories = sinon.fake.returns([]); - await clusterService.pollClusterState(); - state = clusterService.state(); - expect(state).toBeDefined(); - expect(state.clusterMeta.repositories.length).toBe(0); - expect(state.routingTable.repositoryURIs().length).toBe(0); -}); diff --git a/x-pack/legacy/plugins/code/server/distributed/cluster/cluster_service.ts b/x-pack/legacy/plugins/code/server/distributed/cluster/cluster_service.ts deleted file mode 100644 index a5ae0a27e128b..0000000000000 --- a/x-pack/legacy/plugins/code/server/distributed/cluster/cluster_service.ts +++ /dev/null @@ -1,131 +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 util from 'util'; -import { ClusterMetadata } from './cluster_meta'; -import { EsClient } from '../../lib/esqueue'; -import { RepositoryObjectClient } from '../../search'; -import { Poller } from '../../poller'; -import { Logger } from '../../log'; -import { ClusterState } from './cluster_state'; -import { ClusterStateEvent } from './cluster_state_event'; - -/** - * ClusterService synchronizes the core cluster states with the remote, and provides methods to read the current state, - * and to register listeners to watch the state change. - */ -export class ClusterService { - private readonly clusterStateListeners: ClusterStateListener[] = []; - private readonly clusterStatePoller = new Poller({ - functionToPoll: () => { - return this.pollClusterState(); - }, - pollFrequencyInMillis: 5000, - trailing: true, - continuePollingOnError: true, - }); - - private currentState: ClusterState = ClusterState.empty(); - - constructor( - public readonly esClient: EsClient, - public readonly logger: Logger, - private readonly repositoryObjectClient: RepositoryObjectClient = new RepositoryObjectClient( - esClient - ) - ) {} - - public async start() { - this.clusterStatePoller.start(); - } - - public async stop() { - this.clusterStatePoller.stop(); - } - - /** - * Sync the cluster meta-data with the remote storage. - */ - async pollClusterState(): Promise { - const repos = await this.repositoryObjectClient.getAllRepositories(); - const repoUris = new Set(repos.map(repo => repo.uri)); - const currentRepoUris = new Set( - this.currentState.clusterMeta.repositories.map(repo => repo.uri) - ); - const added = new Set(Array.from(repoUris).filter(uri => !currentRepoUris.has(uri))); - const removed = new Set(Array.from(currentRepoUris).filter(uri => !repoUris.has(uri))); - if (added.size === 0 && removed.size === 0) { - // the cluster state and the routing table is only needed to be updated when repository objects have changed. - return; - } - this.setClusterState( - new ClusterState( - new ClusterMetadata(repos), - this.currentState.routingTable.withoutRepositories(removed), - this.currentState.nodes - ) - ); - } - - public state(): ClusterState { - return this.currentState; - } - - public addClusterStateListener(applier: ClusterStateListener) { - this.clusterStateListeners.push(applier); - } - - private async callClusterStateListeners(event: ClusterStateEvent) { - for (const applier of this.clusterStateListeners) { - try { - await applier.onClusterStateChanged(event); - } catch (e) { - this.logger.error(`Failed to apply cluster state ${util.inspect(event)}`); - } - } - } - - /** - * Set the local in memory cluster state, and call cluster state listeners if the state has been changed. - * - * @param newState is the new cluster state to set. - */ - public setClusterState(newState: ClusterState): ClusterStateEvent | undefined { - if (newState === this.currentState) { - return undefined; - } - const event = new ClusterStateEvent(newState, this.currentState); - this.currentState = newState; - setTimeout(async () => { - this.callClusterStateListeners(event); - }); - - return event; - } - - /** - * Invoke the updater with the current cluster state as the parameter, and write the result of the updater to remote - * as the new cluster state, if the current local cluster state matches the current remote cluster state, otherwise - * the updater should be executed again, with the most recent remote cluster state as the input. - * It means the method works as a series of CAS operations, until the first success operation. - * It also means the updater should be side-effect free. - */ - public async updateClusterState( - updater: ClusterStateUpdater - ): Promise { - const newState = updater(this.currentState); - return this.setClusterState(newState); - } -} - -/** - * A cluster applier is a listener of cluster state event. - */ -export interface ClusterStateListener { - onClusterStateChanged(event: ClusterStateEvent): Promise; -} - -export type ClusterStateUpdater = (state: ClusterState) => ClusterState; diff --git a/x-pack/legacy/plugins/code/server/distributed/cluster/cluster_state.ts b/x-pack/legacy/plugins/code/server/distributed/cluster/cluster_state.ts deleted file mode 100644 index a567a626c96bc..0000000000000 --- a/x-pack/legacy/plugins/code/server/distributed/cluster/cluster_state.ts +++ /dev/null @@ -1,59 +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 { RoutingTable } from './routing_table'; -import { Repository } from '../../../model'; -import { ClusterMetadata } from './cluster_meta'; -import { CodeNodes } from './code_nodes'; - -/** - * A snapshot of the meta data and the routing table of the cluster. - */ -export class ClusterState { - constructor( - public readonly clusterMeta: ClusterMetadata, - public readonly routingTable: RoutingTable, - public readonly nodes: CodeNodes - ) {} - - static empty(): ClusterState { - return new ClusterState(new ClusterMetadata([]), new RoutingTable([]), new CodeNodes([])); - } - - /** - * Get all repository objects belongs to the given node id according to the routing table. - * @param nodeId the id of the node. - */ - public getNodeRepositories(nodeId: string): Repository[] { - return this.routingTable - .getRepositoryURIsByNodeId(nodeId) - .map(uri => { - return this.clusterMeta.getRepository(uri); - }) - .filter(repo => { - // the repository uri exists in the routing table, but not exists as a meta object - // it means the routing table is stale - return repo !== undefined; - }) as Repository[]; - } - - /** - * find repositories not exists in the routing table, or the node it assigned to doesn't exists in the cluster - */ - public getUnassignedRepositories(): Repository[] { - return this.clusterMeta.repositories.filter(repo => { - const nodeId = this.routingTable.getNodeIdByRepositoryURI(repo.uri); - if (!nodeId) { - return true; - } - return this.nodes.getNodeById(nodeId) === undefined; - }); - } - - public toString(): string { - return `{routingTable: ${this.routingTable}}`; - } -} diff --git a/x-pack/legacy/plugins/code/server/distributed/cluster/cluster_state_event.ts b/x-pack/legacy/plugins/code/server/distributed/cluster/cluster_state_event.ts deleted file mode 100644 index 7d2eec30c6dc3..0000000000000 --- a/x-pack/legacy/plugins/code/server/distributed/cluster/cluster_state_event.ts +++ /dev/null @@ -1,14 +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 { ClusterState } from './cluster_state'; - -/** - * An object indicates cluster state changes. - */ -export class ClusterStateEvent { - constructor(public readonly current: ClusterState, public readonly prev: ClusterState) {} -} diff --git a/x-pack/legacy/plugins/code/server/distributed/cluster/code_nodes.ts b/x-pack/legacy/plugins/code/server/distributed/cluster/code_nodes.ts deleted file mode 100644 index 6f07d070df579..0000000000000 --- a/x-pack/legacy/plugins/code/server/distributed/cluster/code_nodes.ts +++ /dev/null @@ -1,26 +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 interface CodeNode { - id: string; - address: string; -} - -export class CodeNodes { - private readonly idToNodes: Map; - - constructor(public readonly nodes: CodeNode[]) { - this.idToNodes = new Map( - nodes.map(node => { - return [node.id, node]; - }) - ); - } - - public getNodeById(id: string): CodeNode | undefined { - return this.idToNodes.get(id); - } -} diff --git a/x-pack/legacy/plugins/code/server/distributed/cluster/config_cluster_membership_service.ts b/x-pack/legacy/plugins/code/server/distributed/cluster/config_cluster_membership_service.ts deleted file mode 100644 index f12f132529024..0000000000000 --- a/x-pack/legacy/plugins/code/server/distributed/cluster/config_cluster_membership_service.ts +++ /dev/null @@ -1,49 +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 util from 'util'; -import { ClusterMembershipService } from './cluster_membership_service'; -import { ServerOptions } from '../../server_options'; -import { ClusterService } from './cluster_service'; -import { CodeNode, CodeNodes } from './code_nodes'; -import { ClusterState } from './cluster_state'; -import { Logger } from '../../log'; - -export class ConfigClusterMembershipService implements ClusterMembershipService { - private readonly nodes: CodeNodes; - - public readonly localNode: CodeNode; - - constructor( - private readonly options: ServerOptions, - private readonly clusterService: ClusterService, - private readonly log: Logger - ) { - this.localNode = { - id: options.serverUUID, - address: options.localAddress, - } as CodeNode; - - // if no nodes are configured, add the local node, to be compatible with the single node mode - if (this.options.codeNodes.length > 0) { - this.nodes = new CodeNodes(this.options.codeNodes); - } else { - this.nodes = new CodeNodes([this.localNode]); - } - this.log.info( - `Joined node [${util.inspect(this.localNode)}] to cluster ${util.inspect(this.nodes.nodes)}` - ); - } - - async start(): Promise { - const state = this.clusterService.state(); - this.clusterService.setClusterState( - new ClusterState(state.clusterMeta, state.routingTable, this.nodes) - ); - } - - async stop(): Promise {} -} diff --git a/x-pack/legacy/plugins/code/server/distributed/cluster/hash_schedule_policy.ts b/x-pack/legacy/plugins/code/server/distributed/cluster/hash_schedule_policy.ts deleted file mode 100644 index ee4d475cba0a9..0000000000000 --- a/x-pack/legacy/plugins/code/server/distributed/cluster/hash_schedule_policy.ts +++ /dev/null @@ -1,54 +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 _ from 'lodash'; -import { ResourceSchedulePolicy } from './resource_scheduler'; -import { RoutingTable } from './routing_table'; -import { CodeNode, CodeNodes } from './code_nodes'; - -/** - * A policy that assigns resources to nodes based on hash of the resource id. It is easy and stable for a cluster with - * a static node table. - */ -export class HashSchedulePolicy implements ResourceSchedulePolicy { - canAllocate(resource: string, routingTable: RoutingTable, nodes: CodeNodes): boolean { - return !_.isEmpty(nodes.nodes); - } - - canAllocateOnNode( - resource: string, - node: CodeNode, - routingTable: RoutingTable, - nodes: CodeNodes - ): boolean { - if (_.isEmpty(nodes.nodes)) { - return false; - } - - const sortedNodeIds: string[] = nodes.nodes.map(n => n.id).sort(); - - const hashCode = this.hash(resource); - const targetNodeId = sortedNodeIds[hashCode % sortedNodeIds.length]; - return targetNodeId === node.id; - } - - /** - * To calculate the hash code of the string as s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1], - * see code snippets from https://gist.github.com/hyamamoto/fd435505d29ebfa3d9716fd2be8d42f0. - */ - private hash(s: string): number { - let hashCode = 0; - for (let i = 0; i < s.length; i++) { - // eslint-disable-next-line no-bitwise - hashCode = (Math.imul(31, hashCode) + s.charCodeAt(i)) | 0; - } - return Math.abs(hashCode); - } - - score(resource: string, node: CodeNode, routingTable: RoutingTable, nodes: CodeNodes): number { - return 1; - } -} diff --git a/x-pack/legacy/plugins/code/server/distributed/cluster/node_repositories_service.test.ts b/x-pack/legacy/plugins/code/server/distributed/cluster/node_repositories_service.test.ts deleted file mode 100644 index 17893e0f6b1bc..0000000000000 --- a/x-pack/legacy/plugins/code/server/distributed/cluster/node_repositories_service.test.ts +++ /dev/null @@ -1,122 +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 sinon from 'sinon'; -import { Logger } from '../../log'; -import { ConsoleLoggerFactory } from '../../utils/console_logger_factory'; -import { NodeRepositoriesService } from './node_repositories_service'; -import { ClusterService } from './cluster_service'; -import { ClusterMembershipService } from './cluster_membership_service'; -import { CodeNode, CodeNodes } from './code_nodes'; -import { emptyAsyncFunc } from '../../test_utils'; -import { CloneWorker } from '../../queue'; -import { ClusterStateEvent } from './cluster_state_event'; -import { ClusterState } from './cluster_state'; -import { ClusterMetadata } from './cluster_meta'; -import { Repository } from '../../../model'; -import { ResourceAssignment, RoutingTable } from './routing_table'; - -const log: Logger = new ConsoleLoggerFactory().getLogger(['test']); - -afterEach(() => { - sinon.restore(); -}); - -const cloneWorker = ({ - enqueueJob: emptyAsyncFunc, -} as any) as CloneWorker; - -const clusterService = {} as ClusterService; - -const testNodes = [ - { id: 'node1', address: 'http://node1' } as CodeNode, - { id: 'node2', address: 'http://node2' } as CodeNode, -]; - -const testRepos = [ - { uri: 'test1', url: 'http://test1' } as Repository, - { uri: 'test2', url: 'http://test2' } as Repository, -]; - -test('Enqueue clone job after new repository is added to the local node', async () => { - const enqueueJobSpy = sinon.spy(cloneWorker, 'enqueueJob'); - - const clusterMembershipService = { - localNode: testNodes[0], - } as ClusterMembershipService; - - const nodeService = new NodeRepositoriesService( - log, - clusterService, - clusterMembershipService, - cloneWorker - ); - - // event with no new repositories - let event = new ClusterStateEvent(ClusterState.empty(), ClusterState.empty()); - await nodeService.onClusterStateChanged(event); - expect(enqueueJobSpy.called).toBeFalsy(); - expect(nodeService.localRepos.size).toBe(0); - - // event with a new repository - event = new ClusterStateEvent( - new ClusterState( - new ClusterMetadata([testRepos[0]]), - new RoutingTable([ - { nodeId: testNodes[0].id, resource: testRepos[0].uri } as ResourceAssignment, - ]), - new CodeNodes([testNodes[0]]) - ), - event.current - ); - await nodeService.onClusterStateChanged(event); - expect(enqueueJobSpy.calledOnce).toBeTruthy(); - expect(nodeService.localRepos.size).toBe(1); - - // event with removed repository - event = new ClusterStateEvent(ClusterState.empty(), event.current); - await nodeService.onClusterStateChanged(event); - expect(enqueueJobSpy.calledOnce).toBeTruthy(); - expect(nodeService.localRepos.size).toBe(0); - - // event with two added repositories - event = new ClusterStateEvent( - new ClusterState( - new ClusterMetadata([testRepos[0], testRepos[1]]), - new RoutingTable([ - { nodeId: testNodes[0].id, resource: testRepos[0].uri } as ResourceAssignment, - { nodeId: testNodes[0].id, resource: testRepos[1].uri } as ResourceAssignment, - ]), - new CodeNodes([testNodes[0]]) - ), - event.current - ); - await nodeService.onClusterStateChanged(event); - expect(enqueueJobSpy.callCount).toBe(3); - expect(nodeService.localRepos.size).toBe(2); - - // event with removed repository - event = new ClusterStateEvent(ClusterState.empty(), event.current); - await nodeService.onClusterStateChanged(event); - expect(enqueueJobSpy.callCount).toBe(3); - expect(nodeService.localRepos.size).toBe(0); - - // event with two added repositories, one for the other node - event = new ClusterStateEvent( - new ClusterState( - new ClusterMetadata([testRepos[0], testRepos[1]]), - new RoutingTable([ - { nodeId: testNodes[0].id, resource: testRepos[0].uri } as ResourceAssignment, - { nodeId: testNodes[1].id, resource: testRepos[1].uri } as ResourceAssignment, - ]), - new CodeNodes([testNodes[0]]) - ), - event.current - ); - await nodeService.onClusterStateChanged(event); - expect(enqueueJobSpy.callCount).toBe(4); - expect(nodeService.localRepos.size).toBe(1); -}); diff --git a/x-pack/legacy/plugins/code/server/distributed/cluster/node_repositories_service.ts b/x-pack/legacy/plugins/code/server/distributed/cluster/node_repositories_service.ts deleted file mode 100644 index 7d46960af1bab..0000000000000 --- a/x-pack/legacy/plugins/code/server/distributed/cluster/node_repositories_service.ts +++ /dev/null @@ -1,70 +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 util from 'util'; -import { ClusterService, ClusterStateListener } from './cluster_service'; -import { ClusterStateEvent } from './cluster_state_event'; -import { ClusterMembershipService } from './cluster_membership_service'; -import { CloneWorker } from '../../queue'; -import { Repository, RepositoryUri, RepoState } from '../../../model'; -import { Logger } from '../../log'; - -export class NodeRepositoriesService implements ClusterStateListener { - // visible for test - readonly localRepos = new Map(); - private readonly localNodeId = this.clusterMembershipService.localNode.id; - - constructor( - private readonly log: Logger, - private readonly clusterService: ClusterService, - private readonly clusterMembershipService: ClusterMembershipService, - private readonly cloneWorker: CloneWorker - ) {} - - public async start() { - /** - * we can add locally exists repositories to localRepos when the service is started to avoid unnecessarily add clone - * tasks for them, but for now it's OK because clone job is idempotent. - */ - this.clusterService.addClusterStateListener(this); - } - - public async stop() {} - - async onClusterStateChanged(event: ClusterStateEvent): Promise { - // compare repositories in the cluster state with repositories in the local node, and remove - const repos = event.current.getNodeRepositories(this.clusterMembershipService.localNode.id); - const localNewRepos = repos.filter(repo => !this.localRepos.has(repo.uri)); - const localRemovedRepos = Array.from(this.localRepos.values()).filter( - repo => - event.current.routingTable.getNodeIdByRepositoryURI(repo.metadata.uri) !== this.localNodeId - ); - for (const localNewRepo of localNewRepos) { - this.log.info( - `Repository added to node [${this.localNodeId}]: ${util.inspect(localNewRepo)}` - ); - await this.cloneWorker.enqueueJob({ url: localNewRepo.url }, {}); - this.localRepos.set(localNewRepo.uri, { - metadata: localNewRepo, - currentState: RepoState.CLONING, - }); - } - // TODO remove the stale local repo after the Kibana HA is ready - for (const localRemovedRepo of localRemovedRepos) { - this.log.info( - `Repository removed from node [${this.localNodeId}]: ${util.inspect( - localRemovedRepo.metadata - )}` - ); - this.localRepos.delete(localRemovedRepo.metadata.uri); - } - } -} - -interface LocalRepository { - metadata: Repository; - currentState: RepoState; -} diff --git a/x-pack/legacy/plugins/code/server/distributed/cluster/resource_scheduler.test.ts b/x-pack/legacy/plugins/code/server/distributed/cluster/resource_scheduler.test.ts deleted file mode 100644 index 0b826f60ed8ed..0000000000000 --- a/x-pack/legacy/plugins/code/server/distributed/cluster/resource_scheduler.test.ts +++ /dev/null @@ -1,89 +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 { HashSchedulePolicy } from './hash_schedule_policy'; -import { RoutingTable } from './routing_table'; -import { CodeNode, CodeNodes } from './code_nodes'; -import { ResourceScheduler } from './resource_scheduler'; -import { Logger } from '../../log'; -import { ConsoleLoggerFactory } from '../../utils/console_logger_factory'; -import { ClusterState } from './cluster_state'; -import { ClusterMetadata } from './cluster_meta'; - -const log: Logger = new ConsoleLoggerFactory().getLogger(['test']); - -function randStr() { - return Math.random() - .toString(36) - .substr(2, 9); -} - -test('test hash schedule policy', () => { - const nodeA = { id: 'na', address: 'http://a' } as CodeNode; - const nodeB = { id: 'nb', address: 'http://b' } as CodeNode; - const nodeC = { id: 'nc', address: 'http://c' } as CodeNode; - - const policy = new HashSchedulePolicy(); - const rt = new RoutingTable(); - - // can allocate only when any node exists - expect(policy.canAllocate('ra', rt, new CodeNodes([]))).toBeFalsy(); - expect(policy.canAllocate('ra', rt, new CodeNodes([nodeA]))).toBeTruthy(); - - // can allocate when the node id matches - expect(policy.canAllocateOnNode('ra', nodeB, rt, new CodeNodes([nodeA]))).toBeFalsy(); - expect(policy.canAllocateOnNode('ra', nodeA, rt, new CodeNodes([nodeA]))).toBeTruthy(); - - const nodes1 = new CodeNodes([nodeA, nodeB, nodeC]); - const nodes2 = new CodeNodes([nodeB, nodeA, nodeC]); - const nodes3 = new CodeNodes([nodeB, nodeC, nodeA]); - - // always can only be allocated on one of the nodes - for (let i = 0; i < 100; i++) { - const resName = randStr(); - let target: CodeNode | undefined; - for (const nodes of [nodes1, nodes2, nodes3]) { - let num = 0; - for (const node of nodes.nodes) { - if (policy.canAllocateOnNode(resName, node, rt, nodes)) { - num++; - if (target) { - // sequence of the nodes doesn't matter - expect(node).toBe(target); - } else { - target = node; - } - } - } - expect(num).toBe(1); - } - } -}); - -test('hash based resource allocation', () => { - const scheduler = new ResourceScheduler([new HashSchedulePolicy()], log); - - const rt = new RoutingTable(); - let state = ClusterState.empty(); - const resources = ['ra', 'tb']; - - let assignments = scheduler.allocate(resources, state); - expect(assignments.length).toBe(0); - - let nodes = new CodeNodes([{ id: 'na', address: 'http://a' }]); - state = new ClusterState(new ClusterMetadata(), rt, nodes); - assignments = scheduler.allocate(resources, state); - - expect(assignments.length).toBe(resources.length); - - nodes = new CodeNodes([{ id: 'na', address: 'http://a' }, { id: 'nb', address: 'http://b' }]); - state = new ClusterState(new ClusterMetadata(), rt, nodes); - for (let i = 0; i < 10; i++) { - resources.push(randStr()); - assignments = scheduler.allocate(resources, state); - expect(assignments.length).toBe(resources.length); - } -}); diff --git a/x-pack/legacy/plugins/code/server/distributed/cluster/resource_scheduler.ts b/x-pack/legacy/plugins/code/server/distributed/cluster/resource_scheduler.ts deleted file mode 100644 index 06aa07dc730c9..0000000000000 --- a/x-pack/legacy/plugins/code/server/distributed/cluster/resource_scheduler.ts +++ /dev/null @@ -1,113 +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 _ from 'lodash'; -import { ResourceAssignment, RoutingTable } from './routing_table'; -import { CodeNode, CodeNodes } from './code_nodes'; -import { ClusterState } from './cluster_state'; -import { Logger } from '../../log'; - -/** - * The scheduler decides how to allocate resources to nodes. Allocating resources to nodes basically follows 2 steps: - * - Filter: find nodes that is applicable for the resource, based on attributes of nodes and restrictions of policies. - * - Score: score each applicable node for the resource, and return a list of applicable nodes sorted by the score in the - * descending order. - * - Decide: pick the best fit node from the list as the candidate. - */ -export class ResourceScheduler { - constructor(private readonly policies: ResourceSchedulePolicy[], private readonly log: Logger) {} - - public allocate(resources: string[], state: ClusterState): ResourceAssignment[] { - if (_.isEmpty(resources)) { - return []; - } - if (_.isEmpty(state.nodes.nodes)) { - return []; - } - // remove repos cannot be allocated - const allocatableRepos = resources.filter(repo => { - return _.every(this.policies, policy => - policy.canAllocate(repo, state.routingTable, state.nodes) - ); - }); - if (_.isEmpty(allocatableRepos)) { - return []; - } - const assignments: ResourceAssignment[] = []; - let routingTable = state.routingTable; - for (const resource of resources) { - const assignment = this.allocateResource(resource, state.nodes, routingTable); - if (!assignment) { - continue; - } - assignments.push(assignment); - routingTable = routingTable.assign([assignment]); - this.log.info(`Assigned resource [${resource}] to node [${assignment.nodeId}]`); - } - return assignments; - } - - protected allocateResource( - resource: string, - nodes: CodeNodes, - routingTable: RoutingTable - ): ResourceAssignment | undefined { - const scoredNodes = nodes.nodes - .filter(node => { - // remove nodes that the - return _.every(this.policies, policy => - policy.canAllocateOnNode(resource, node, routingTable, nodes) - ); - }) - .map(node => { - const score = this.policies.reduce( - (prev, policy) => prev * policy.score(resource, node, routingTable, nodes), - 1 - ); - return { node, score }; - }); - if (_.isEmpty(scoredNodes)) { - return undefined; - } - - let bestFit = scoredNodes[0]; - for (const node of scoredNodes) { - if (node.score >= bestFit.score) { - // use the node id as the tie-breaker - if (node.node.id > bestFit.node.id) { - bestFit = node; - } - } - } - - return { - nodeId: bestFit.node.id, - resource, - }; - } -} - -export interface ResourceSchedulePolicy { - /** - * Decide whether the resource can be allocated to any node, according to the current cluster state. - */ - canAllocate(resource: string, routingTable: RoutingTable, nodes: CodeNodes): boolean; - - /** - * Decide whether the resource can be allocated to the node, according to the current cluster state. - */ - canAllocateOnNode( - resource: string, - node: CodeNode, - routingTable: RoutingTable, - nodes: CodeNodes - ): boolean; - - /** - * Calculate the score of the assignment, the higher the better. - */ - score(resource: string, node: CodeNode, routingTable: RoutingTable, nodes: CodeNodes): number; -} diff --git a/x-pack/legacy/plugins/code/server/distributed/cluster/resource_scheduler_service.ts b/x-pack/legacy/plugins/code/server/distributed/cluster/resource_scheduler_service.ts deleted file mode 100644 index 3249c8813a4d7..0000000000000 --- a/x-pack/legacy/plugins/code/server/distributed/cluster/resource_scheduler_service.ts +++ /dev/null @@ -1,62 +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 _ from 'lodash'; -import { ClusterService, ClusterStateListener } from './cluster_service'; -import { ClusterStateEvent } from './cluster_state_event'; -import { ClusterState } from './cluster_state'; -import { ResourceScheduler } from './resource_scheduler'; -import { Logger } from '../../log'; -import { HashSchedulePolicy } from './hash_schedule_policy'; - -/** - * This service watches the change of the cluster state and allocates resources to nodes if needed: - * - when a new resource is added to the cluster - * - a resource is unassigned, e.g., due to disk watermark constraints - * - when a node is removed from the cluster (HA) - */ -export class ResourceSchedulerService implements ClusterStateListener { - private readonly scheduler = new ResourceScheduler([new HashSchedulePolicy()], this.log); - - constructor(private readonly clusterService: ClusterService, private readonly log: Logger) { - this.clusterService.addClusterStateListener(this); - } - - public async start() {} - - public async stop() {} - - public async allocateUnassigned(state?: ClusterState) { - if (!state) { - state = this.clusterService.state(); - } - const unassignedRepos = state.getUnassignedRepositories(); - if (_.isEmpty(unassignedRepos)) { - return; - } - await this.clusterService.updateClusterState(current => { - // sort repositories by names to make the allocation result stable - const unassigned = current - .getUnassignedRepositories() - .map(repo => repo.uri) - .sort(); - - const assignments = this.scheduler.allocate(unassigned, current); - if (_.isEmpty(assignments)) { - return current; - } - return new ClusterState( - current.clusterMeta, - current.routingTable.assign(assignments), - current.nodes - ); - }); - } - - async onClusterStateChanged(event: ClusterStateEvent): Promise { - await this.allocateUnassigned(event.current); - } -} diff --git a/x-pack/legacy/plugins/code/server/distributed/cluster/routing_table.ts b/x-pack/legacy/plugins/code/server/distributed/cluster/routing_table.ts deleted file mode 100644 index b93b2f2bc344f..0000000000000 --- a/x-pack/legacy/plugins/code/server/distributed/cluster/routing_table.ts +++ /dev/null @@ -1,76 +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. - */ - -/** - * A snapshot of the routing table, which defines on which node each repository resides. - * - * Currently, the relationship between nodes and repositories is one to many. - */ -export class RoutingTable { - private readonly node2Repos: Map; - private readonly repo2Node: Map; - - constructor(private readonly assignments: ResourceAssignment[] = []) { - this.repo2Node = new Map(assignments.map(t => [t.resource, t.nodeId])); - this.node2Repos = assignments.reduce(function(map, assignment) { - let arr = map.get(assignment.nodeId); - if (!arr) { - arr = []; - map.set(assignment.nodeId, arr); - } - arr.push(assignment.resource); - return map; - }, new Map()); - } - - public assign(assignments: ResourceAssignment[]): RoutingTable { - return new RoutingTable(this.assignments.concat(assignments)); - } - - /** - * return a new routing table without given repositories - */ - public withoutRepositories(repoUris: Set): RoutingTable { - return new RoutingTable( - this.assignments.filter(assignment => !repoUris.has(assignment.resource)) - ); - } - - public nodeIds(): string[] { - return Array.from(this.node2Repos.keys()); - } - - public repositoryURIs(): string[] { - return Array.from(this.repo2Node.keys()); - } - - public getNodeIdByRepositoryURI(repoURI: string): string | undefined { - return this.repo2Node.get(repoURI); - } - - public getRepositoryURIsByNodeId(nodeId: string): string[] { - return this.node2Repos.get(nodeId) || []; - } - - public toString(): string { - let str: string = '['; - let first: boolean = true; - this.repo2Node.forEach((v, k, m) => { - if (first) { - first = false; - } else { - str += ', '; - } - str += `${k}: ${v}`; - }); - return str + ']'; - } -} - -export interface ResourceAssignment { - nodeId: string; - resource: string; -} diff --git a/x-pack/legacy/plugins/code/server/distributed/code_services.test.ts b/x-pack/legacy/plugins/code/server/distributed/code_services.test.ts deleted file mode 100644 index bcc2e7b21e672..0000000000000 --- a/x-pack/legacy/plugins/code/server/distributed/code_services.test.ts +++ /dev/null @@ -1,116 +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 { KibanaRequest } from 'src/core/server'; -import { httpServiceMock, httpServerMock } from 'src/core/server/mocks'; -import { createTestHapiServer } from '../test_utils'; -import { LocalHandlerAdapter } from './local_handler_adapter'; -import { CodeServerRouter } from '../security'; -import { RequestContext, ServiceHandlerFor } from './service_definition'; -import { CodeNodeAdapter, RequestPayload } from './multinode/code_node_adapter'; -import { DEFAULT_SERVICE_OPTION } from './service_handler_adapter'; -import { NonCodeNodeAdapter } from './multinode/non_code_node_adapter'; -import { CodeServices } from './code_services'; -import { Logger } from '../log'; -import { ConsoleLoggerFactory } from '../utils/console_logger_factory'; - -const log: Logger = new ConsoleLoggerFactory().getLogger(['test']); -let hapiServer = createTestHapiServer(); - -const routerMock = httpServiceMock.createRouter(); -let router: CodeServerRouter = new CodeServerRouter(routerMock); -beforeEach(async () => { - hapiServer = createTestHapiServer(); - router = new CodeServerRouter(routerMock); -}); -const TestDefinition = { - test1: { - request: {} as { name: string }, - response: {} as { result: string }, - }, - test2: { - request: {}, - response: {} as RequestContext, - routePath: 'userDefinedPath', - }, -}; - -export const testServiceHandler: ServiceHandlerFor = { - async test1({ name }) { - return { result: `hello ${name}` }; - }, - async test2(_, context: RequestContext) { - return context; - }, -}; - -test('local adapter should work', async () => { - const services = new CodeServices(new LocalHandlerAdapter()); - services.registerHandler(TestDefinition, testServiceHandler); - const testApi = services.serviceFor(TestDefinition); - const endpoint = await services.locate(httpServerMock.createKibanaRequest(), ''); - const { result } = await testApi.test1(endpoint, { name: 'tester' }); - expect(result).toBe(`hello tester`); -}); - -test.skip('multi-node adapter should register routes', async () => { - const services = new CodeServices(new CodeNodeAdapter(router, log)); - services.registerHandler(TestDefinition, testServiceHandler); - const prefix = DEFAULT_SERVICE_OPTION.routePrefix; - - const path1 = `${prefix}/test1`; - const response = await hapiServer.inject({ - method: 'POST', - url: path1, - payload: { params: { name: 'tester' } }, - }); - expect(response.statusCode).toBe(200); - const { data } = JSON.parse(response.payload); - expect(data.result).toBe(`hello tester`); -}); - -test.skip('non-code-node could send request to code-node', async () => { - const codeNode = new CodeServices(new CodeNodeAdapter(router, log)); - const codeNodeUrl = 'http://localhost:5601'; - const nonCodeNodeAdapter = new NonCodeNodeAdapter(codeNodeUrl, log); - const nonCodeNode = new CodeServices(nonCodeNodeAdapter); - // replace client request fn to hapi.inject - nonCodeNodeAdapter.requestFn = async ( - baseUrl: string, - path: string, - payload: RequestPayload, - originRequest: KibanaRequest - ) => { - expect(baseUrl).toBe(codeNodeUrl); - const response = await hapiServer.inject({ - method: 'POST', - url: path, - headers: originRequest.headers as any, - payload, - }); - expect(response.statusCode).toBe(200); - return JSON.parse(response.payload); - }; - codeNode.registerHandler(TestDefinition, testServiceHandler); - nonCodeNode.registerHandler(TestDefinition, null); - const testApi = nonCodeNode.serviceFor(TestDefinition); - const fakeRequest = ({ - route: { - path: 'fakePath', - }, - headers: { - fakeHeader: 'fakeHeaderValue', - }, - } as unknown) as KibanaRequest; - const fakeResource = 'fakeResource'; - const endpoint = await nonCodeNode.locate(fakeRequest, fakeResource); - const { result } = await testApi.test1(endpoint, { name: 'tester' }); - expect(result).toBe(`hello tester`); - - const context = await testApi.test2(endpoint, {}); - expect(context.resource).toBe(fakeResource); - expect(context.path).toBe(fakeRequest.route.path); -}); diff --git a/x-pack/legacy/plugins/code/server/distributed/code_services.ts b/x-pack/legacy/plugins/code/server/distributed/code_services.ts deleted file mode 100644 index a2abe402a8e52..0000000000000 --- a/x-pack/legacy/plugins/code/server/distributed/code_services.ts +++ /dev/null @@ -1,50 +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 { KibanaRequest } from 'src/core/server'; -import { ServiceDefinition, ServiceHandlerFor, ServiceMethodMap } from './service_definition'; -import { - DEFAULT_SERVICE_OPTION, - ServiceHandlerAdapter, - ServiceRegisterOptions, -} from './service_handler_adapter'; -import { Endpoint } from './resource_locator'; - -export class CodeServices { - constructor(private readonly adapter: ServiceHandlerAdapter) {} - - public registerHandler( - serviceDefinition: serviceDefinition, - serviceHandler: ServiceHandlerFor | null, - options: ServiceRegisterOptions = DEFAULT_SERVICE_OPTION - ) { - this.adapter.registerHandler(serviceDefinition, serviceHandler, options); - } - - public async start() { - await this.adapter.start(); - } - - public async stop() { - await this.adapter.stop(); - } - - public allocate(req: KibanaRequest, resource: string): Promise { - return this.adapter.locator.allocate(req, resource); - } - - public locate(req: KibanaRequest, resource: string): Promise { - return this.adapter.locator.locate(req, resource); - } - - public isResourceLocal(resource: string): Promise { - return this.adapter.locator.isResourceLocal(resource); - } - - public serviceFor(serviceDefinition: def): ServiceMethodMap { - return this.adapter.getService(serviceDefinition); - } -} diff --git a/x-pack/legacy/plugins/code/server/distributed/local_endpoint.ts b/x-pack/legacy/plugins/code/server/distributed/local_endpoint.ts deleted file mode 100644 index 8897ec8ae3acb..0000000000000 --- a/x-pack/legacy/plugins/code/server/distributed/local_endpoint.ts +++ /dev/null @@ -1,20 +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 { KibanaRequest } from 'src/core/server'; -import { Endpoint } from './resource_locator'; -import { RequestContext } from './service_definition'; - -export class LocalEndpoint implements Endpoint { - constructor(public readonly httpRequest: KibanaRequest, public readonly resource: string) {} - - toContext(): RequestContext { - return { - resource: this.resource, - path: this.httpRequest.route.path, - } as RequestContext; - } -} diff --git a/x-pack/legacy/plugins/code/server/distributed/local_handler_adapter.ts b/x-pack/legacy/plugins/code/server/distributed/local_handler_adapter.ts deleted file mode 100644 index 4f51ee2938366..0000000000000 --- a/x-pack/legacy/plugins/code/server/distributed/local_handler_adapter.ts +++ /dev/null @@ -1,60 +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 { KibanaRequest } from 'src/core/server'; -import { ServiceHandlerAdapter } from './service_handler_adapter'; -import { ServiceDefinition, ServiceHandlerFor, ServiceMethodMap } from './service_definition'; -import { Endpoint, ResourceLocator } from './resource_locator'; -import { LocalEndpoint } from './local_endpoint'; - -export class LocalHandlerAdapter implements ServiceHandlerAdapter { - handlers: Map = new Map(); - - async start(): Promise {} - - async stop(): Promise {} - - registerHandler( - serviceDefinition: def, - serviceHandler: ServiceHandlerFor | null - ) { - if (!serviceHandler) { - throw new Error("Local service handler can't be null!"); - } - const dispatchedHandler: { [key: string]: any } = {}; - // eslint-disable-next-line guard-for-in - for (const method in serviceDefinition) { - dispatchedHandler[method] = function(endpoint: Endpoint, params: any) { - return serviceHandler[method](params, endpoint.toContext()); - }; - } - this.handlers.set(serviceDefinition, dispatchedHandler); - return dispatchedHandler as ServiceMethodMap; - } - - getService(serviceDefinition: def): ServiceMethodMap { - const serviceHandler = this.handlers.get(serviceDefinition); - if (serviceHandler) { - return serviceHandler as ServiceMethodMap; - } else { - throw new Error(`handler for ${serviceDefinition} not found`); - } - } - - locator: ResourceLocator = { - async locate(httpRequest: KibanaRequest, resource: string): Promise { - return Promise.resolve(new LocalEndpoint(httpRequest, resource)); - }, - - isResourceLocal(resource: string): Promise { - return Promise.resolve(true); - }, - - async allocate(httpRequest: KibanaRequest, resource: string): Promise { - return Promise.resolve(new LocalEndpoint(httpRequest, resource)); - }, - }; -} diff --git a/x-pack/legacy/plugins/code/server/distributed/multinode/code_node_adapter.ts b/x-pack/legacy/plugins/code/server/distributed/multinode/code_node_adapter.ts deleted file mode 100644 index a7d2edf4b0308..0000000000000 --- a/x-pack/legacy/plugins/code/server/distributed/multinode/code_node_adapter.ts +++ /dev/null @@ -1,104 +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 { KibanaRequest, KibanaResponseFactory, RequestHandlerContext } from 'src/core/server'; -import util from 'util'; -import Boom from 'boom'; -import { - DEFAULT_SERVICE_OPTION, - ServiceHandlerAdapter, - ServiceRegisterOptions, -} from '../service_handler_adapter'; -import { Endpoint, ResourceLocator } from '../resource_locator'; -import { - RequestContext, - ServiceDefinition, - ServiceHandlerFor, - ServiceMethodMap, -} from '../service_definition'; -import { CodeServerRouter } from '../../security'; -import { LocalHandlerAdapter } from '../local_handler_adapter'; -import { LocalEndpoint } from '../local_endpoint'; -import { Logger } from '../../log'; - -export interface RequestPayload { - context: RequestContext; - params: any; -} - -export class CodeNodeAdapter implements ServiceHandlerAdapter { - localAdapter: LocalHandlerAdapter = new LocalHandlerAdapter(); - constructor(private readonly router: CodeServerRouter, private readonly log: Logger) {} - - locator: ResourceLocator = { - async locate(httpRequest: KibanaRequest, resource: string): Promise { - return Promise.resolve(new LocalEndpoint(httpRequest, resource)); - }, - - isResourceLocal(resource: string): Promise { - return Promise.resolve(false); - }, - - async allocate(httpRequest: KibanaRequest, resource: string): Promise { - return Promise.resolve(new LocalEndpoint(httpRequest, resource)); - }, - }; - - async start(): Promise {} - - async stop(): Promise {} - - getService(serviceDefinition: def): ServiceMethodMap { - // services on code node dispatch to local directly - return this.localAdapter.getService(serviceDefinition); - } - - registerHandler( - serviceDefinition: def, - serviceHandler: ServiceHandlerFor | null, - options: ServiceRegisterOptions = DEFAULT_SERVICE_OPTION - ) { - if (!serviceHandler) { - throw new Error("Code node service handler can't be null!"); - } - const serviceMethodMap = this.localAdapter.registerHandler(serviceDefinition, serviceHandler); - // eslint-disable-next-line guard-for-in - for (const method in serviceDefinition) { - const d = serviceDefinition[method]; - const path = `${options.routePrefix}/${d.routePath || method}`; - // register routes, receive requests from non-code node. - this.router.route({ - method: 'post', - path, - npHandler: async ( - ctx: RequestHandlerContext, - req: KibanaRequest, - res: KibanaResponseFactory - ) => { - // @ts-ignore - const { context, params } = req.body as RequestPayload; - this.log.debug(`Receiving RPC call ${req.url.path} ${util.inspect(params)}`); - const endpoint: Endpoint = { - toContext(): RequestContext { - return context; - }, - }; - try { - const data = await serviceMethodMap[method](endpoint, params); - return res.ok({ body: data }); - } catch (e) { - if (!Boom.isBoom(e)) { - throw Boom.boomify(e, { statusCode: 500 }); - } else { - throw e; - } - } - }, - }); - } - return serviceMethodMap; - } -} diff --git a/x-pack/legacy/plugins/code/server/distributed/multinode/code_node_endpoint.ts b/x-pack/legacy/plugins/code/server/distributed/multinode/code_node_endpoint.ts deleted file mode 100644 index 03c4917dfb732..0000000000000 --- a/x-pack/legacy/plugins/code/server/distributed/multinode/code_node_endpoint.ts +++ /dev/null @@ -1,18 +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 { KibanaRequest } from 'src/core/server'; -import { LocalEndpoint } from '../local_endpoint'; - -export class CodeNodeEndpoint extends LocalEndpoint { - constructor( - public readonly httpRequest: KibanaRequest, - public readonly resource: string, - public readonly codeNodeUrl: string - ) { - super(httpRequest, resource); - } -} diff --git a/x-pack/legacy/plugins/code/server/distributed/multinode/code_node_resource_locator.ts b/x-pack/legacy/plugins/code/server/distributed/multinode/code_node_resource_locator.ts deleted file mode 100644 index e4b3d21b80ec7..0000000000000 --- a/x-pack/legacy/plugins/code/server/distributed/multinode/code_node_resource_locator.ts +++ /dev/null @@ -1,25 +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 { KibanaRequest } from 'src/core/server'; -import { Endpoint, ResourceLocator } from '../resource_locator'; -import { CodeNodeEndpoint } from './code_node_endpoint'; - -export class CodeNodeResourceLocator implements ResourceLocator { - constructor(private readonly codeNodeUrl: string) {} - - async locate(httpRequest: KibanaRequest, resource: string): Promise { - return Promise.resolve(new CodeNodeEndpoint(httpRequest, resource, this.codeNodeUrl)); - } - - isResourceLocal(resource: string): Promise { - return Promise.resolve(false); - } - - allocate(req: KibanaRequest, resource: string): Promise { - return this.locate(req, resource); - } -} diff --git a/x-pack/legacy/plugins/code/server/distributed/multinode/non_code_node_adapter.ts b/x-pack/legacy/plugins/code/server/distributed/multinode/non_code_node_adapter.ts deleted file mode 100644 index 1221651bc51e2..0000000000000 --- a/x-pack/legacy/plugins/code/server/distributed/multinode/non_code_node_adapter.ts +++ /dev/null @@ -1,120 +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 Wreck from '@hapi/wreck'; -import util from 'util'; -import Boom from 'boom'; -import { KibanaRequest } from 'src/core/server'; -import * as http from 'http'; -import { - DEFAULT_SERVICE_OPTION, - ServiceHandlerAdapter, - ServiceRegisterOptions, -} from '../service_handler_adapter'; -import { ResourceLocator } from '../resource_locator'; -import { ServiceDefinition, ServiceHandlerFor, ServiceMethodMap } from '../service_definition'; -import { CodeNodeResourceLocator } from './code_node_resource_locator'; -import { CodeNodeEndpoint } from './code_node_endpoint'; -import { RequestPayload } from './code_node_adapter'; -import { Logger } from '../../log'; - -const pickHeaders = ['authorization']; - -function filterHeaders(originRequest: KibanaRequest) { - const result: { [name: string]: string | string[] | undefined } = {}; - for (const header of pickHeaders) { - if (originRequest.headers[header]) { - result[header] = originRequest.headers[header]; - } - } - return result; -} - -export class NonCodeNodeAdapter implements ServiceHandlerAdapter { - handlers: Map = new Map(); - - constructor(private readonly codeNodeUrl: string, private readonly log: Logger) {} - - locator: ResourceLocator = new CodeNodeResourceLocator(this.codeNodeUrl); - - async start(): Promise {} - - async stop(): Promise {} - - getService(serviceDefinition: def): ServiceMethodMap { - const serviceHandler = this.handlers.get(serviceDefinition); - if (!serviceHandler) { - // we don't need implement code for service, so we can register here. - this.registerHandler(serviceDefinition, null); - } - return serviceHandler as ServiceMethodMap; - } - - registerHandler( - serviceDefinition: def, - // serviceHandler will always be null here since it will be overridden by the request forwarding. - serviceHandler: ServiceHandlerFor | null, - options: ServiceRegisterOptions = DEFAULT_SERVICE_OPTION - ) { - const dispatchedHandler: { [key: string]: any } = {}; - // eslint-disable-next-line guard-for-in - for (const method in serviceDefinition) { - const d = serviceDefinition[method]; - const path = `${options.routePrefix}/${d.routePath || method}`; - dispatchedHandler[method] = async (endpoint: CodeNodeEndpoint, params: any) => { - const payload = { - context: endpoint.toContext(), - params, - }; - const { data } = await this.requestFn( - endpoint.codeNodeUrl, - path, - payload, - endpoint.httpRequest - ); - return data; - }; - } - this.handlers.set(serviceDefinition, dispatchedHandler); - return dispatchedHandler as ServiceMethodMap; - } - - async requestFn( - baseUrl: string, - path: string, - payload: RequestPayload, - originRequest: KibanaRequest - ) { - const opt = { - baseUrl, - payload: JSON.stringify(payload), - // redirect all headers to CodeNode - headers: { ...filterHeaders(originRequest), 'kbn-xsrf': 'kibana' }, - }; - const promise = Wreck.request('POST', path, opt); - const res: http.IncomingMessage = await promise; - this.log.debug(`sending RPC call to ${baseUrl}${path} ${res.statusCode}`); - if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) { - const buffer: Buffer = await Wreck.read(res, {}); - try { - return JSON.parse(buffer.toString(), (key, value) => { - return value && value.type === 'Buffer' ? Buffer.from(value.data) : value; - }); - } catch (e) { - this.log.error('parse json failed: ' + buffer.toString()); - throw Boom.boomify(e, { statusCode: 500 }); - } - } else { - this.log.error( - `received ${res.statusCode} from ${baseUrl}/${path}, params was ${util.inspect( - payload.params - )}` - ); - const body: Boom.Payload = await Wreck.read(res, { json: true }); - throw new Boom(body.message, { statusCode: res.statusCode || 500, data: body.error }); - } - } -} diff --git a/x-pack/legacy/plugins/code/server/distributed/resource_locator.ts b/x-pack/legacy/plugins/code/server/distributed/resource_locator.ts deleted file mode 100644 index 287e36982cbfd..0000000000000 --- a/x-pack/legacy/plugins/code/server/distributed/resource_locator.ts +++ /dev/null @@ -1,29 +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 { KibanaRequest } from 'src/core/server'; -import { RequestContext } from './service_definition'; - -export interface Endpoint { - toContext(): RequestContext; -} - -export interface ResourceLocator { - locate(req: KibanaRequest, resource: string): Promise; - - /** - * Returns whether the resource resides on the local node. This should support both url and uri of the repository. - * - * @param resource the name of the resource. - */ - isResourceLocal(resource: string): Promise; - - /** - * Allocates the resource to nodes and returns the endpoint corresponds to the allocated node. - * If the resource cannot be allocated to any node, it returns undefined. - */ - allocate(req: KibanaRequest, resource: string): Promise; -} diff --git a/x-pack/legacy/plugins/code/server/distributed/service_definition.ts b/x-pack/legacy/plugins/code/server/distributed/service_definition.ts deleted file mode 100644 index a3aa6643c76d0..0000000000000 --- a/x-pack/legacy/plugins/code/server/distributed/service_definition.ts +++ /dev/null @@ -1,55 +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 { Endpoint } from './resource_locator'; - -export interface ServiceDefinition { - [method: string]: { request: any; response: any; routePath?: string }; -} - -export type MethodsFor = Extract< - keyof serviceDefinition, - string ->; - -export type MethodHandler< - serviceDefinition extends ServiceDefinition, - method extends MethodsFor -> = ( - request: RequestFor, - context: RequestContext -) => Promise>; - -export type ServiceHandlerFor = { - [method in MethodsFor]: MethodHandler; -}; - -export type RequestFor< - serviceDefinition extends ServiceDefinition, - method extends MethodsFor -> = serviceDefinition[method]['request']; - -export type ResponseFor< - serviceDefinition extends ServiceDefinition, - method extends MethodsFor -> = serviceDefinition[method]['response']; - -export interface RequestContext { - path: string; - resource: string; -} - -export type ServiceMethodMap = { - [method in MethodsFor]: ServiceMethod; -}; - -export type ServiceMethod< - serviceDefinition extends ServiceDefinition, - method extends MethodsFor -> = ( - endpoint: Endpoint, - request: RequestFor -) => Promise>; diff --git a/x-pack/legacy/plugins/code/server/distributed/service_handler_adapter.ts b/x-pack/legacy/plugins/code/server/distributed/service_handler_adapter.ts deleted file mode 100644 index be19d052b39bd..0000000000000 --- a/x-pack/legacy/plugins/code/server/distributed/service_handler_adapter.ts +++ /dev/null @@ -1,28 +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 { ServiceDefinition, ServiceHandlerFor, ServiceMethodMap } from './service_definition'; -import { ResourceLocator } from './resource_locator'; - -export interface ServiceRegisterOptions { - routePrefix?: string; -} - -export const DEFAULT_SERVICE_OPTION: ServiceRegisterOptions = { - routePrefix: '/api/code/internal', -}; - -export interface ServiceHandlerAdapter { - locator: ResourceLocator; - getService(serviceDefinition: DEF): ServiceMethodMap; - registerHandler( - serviceDefinition: DEF, - serviceHandler: ServiceHandlerFor | null, - options: ServiceRegisterOptions - ): ServiceMethodMap; - start(): Promise; - stop(): Promise; -} diff --git a/x-pack/legacy/plugins/code/server/git_operations.ts b/x-pack/legacy/plugins/code/server/git_operations.ts deleted file mode 100644 index f8e9dc5e589a0..0000000000000 --- a/x-pack/legacy/plugins/code/server/git_operations.ts +++ /dev/null @@ -1,558 +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. - */ - -/* eslint-disable @typescript-eslint/camelcase */ - -import { FileItem, LsTreeSummary, simplegit, SimpleGit } from '@elastic/simple-git/dist'; -import Boom from 'boom'; -import * as Path from 'path'; -import * as fs from 'fs'; -import { isBinaryFileSync } from 'isbinaryfile'; -import { BlameSummary, DiffResultTextFile } from '@elastic/simple-git/dist/response'; -import moment from 'moment'; -import { GitBlame } from '../common/git_blame'; -import { CommitDiff, Diff, DiffKind } from '../common/git_diff'; -import { FileTree, FileTreeItemType, RepositoryUri } from '../model'; -import { CommitInfo, ReferenceInfo, ReferenceType } from '../model/commit'; -import { detectLanguage } from './utils/detect_language'; -import { FormatParser } from './utils/format_parser'; - -export const HEAD = 'HEAD'; -export const DEFAULT_TREE_CHILDREN_LIMIT = 50; - -export interface Blob { - isBinary(): boolean; - content(): Buffer; - rawsize(): number; -} - -function entry2Tree(entry: FileItem, prefixPath: string = ''): FileTree { - const type: FileTreeItemType = GitOperations.mode2type(entry.mode); - const { path, id } = entry; - return { - name: path, - path: prefixPath ? prefixPath + '/' + path : path, - sha1: id, - type, - }; -} - -interface Tree { - entries: FileItem[]; - oid: string; -} -export class GitOperations { - private repoRoot: string; - - constructor(repoRoot: string) { - this.repoRoot = repoRoot; - } - - public async fileContent(uri: RepositoryUri, path: string, revision: string = 'master') { - const git = await this.openGit(uri); - const commit: CommitInfo = await this.getCommitOr404(uri, revision); - const p = `${commit.id}:${path}`; - - const type = await git.catFile(['-t', p]); - if (type.trim() === 'blob') { - const buffer: Buffer = await git.binaryCatFile(['blob', p]); - return { - content(): Buffer { - return buffer; - }, - rawsize(): number { - return buffer.length; - }, - isBinary(): boolean { - return isBinaryFileSync(buffer); - }, - } as Blob; - } else { - throw Boom.unsupportedMediaType(`${uri}/${path} is not a file.`); - } - } - - public async getDefaultBranch(uri: RepositoryUri): Promise { - const git = await this.openGit(uri); - return (await git.raw(['symbolic-ref', HEAD, '--short'])).trim(); - } - - public async getHeadRevision(uri: RepositoryUri): Promise { - return await this.getRevision(uri, HEAD); - } - - public async getRevision(uri: RepositoryUri, ref: string): Promise { - const git = await this.openGit(uri); - return await git.revparse([ref]); - } - - public async blame(uri: RepositoryUri, revision: string, path: string): Promise { - const git = await this.openGit(uri); - const blameSummary: BlameSummary = await git.blame(revision, path); - const results: GitBlame[] = []; - for (const blame of blameSummary.blames) { - results.push({ - committer: { - name: blame.commit.author!.name, - email: blame.commit.author!.email, - }, - startLine: blame.resultLine, - lines: blame.lines, - commit: { - id: blame.commit.id!, - message: blame.commit.message!, - date: moment - .unix(blame.commit.author!.time) - .utcOffset(blame.commit.author!.tz) - .toISOString(true), - }, - }); - } - return results; - } - - public async openGit(uri: RepositoryUri, check: boolean = true): Promise { - const repoDir = this.repoDir(uri); - const git = simplegit(repoDir); - if (!check) return git; - if (await git.checkIsRepo()) { - return git; - } else { - throw Boom.notFound(`repo ${uri} not found`); - } - } - - private repoDir(uri: RepositoryUri) { - const repoDir = Path.join(this.repoRoot, uri); - this.checkPath(repoDir); - return repoDir; - } - - private checkPath(path: string) { - if (!fs.realpathSync(path).startsWith(fs.realpathSync(this.repoRoot))) { - throw new Error('invalid path'); - } - } - - public async countRepoFiles(uri: RepositoryUri, revision: string): Promise { - const git = await this.openGit(uri); - const ls = new LsTreeSummary(git, revision, '.', { recursive: true }); - return (await ls.allFiles()).length; - } - - public async *iterateRepo(uri: RepositoryUri, revision: string) { - const git = await this.openGit(uri); - const ls = new LsTreeSummary(git, revision, '.', { showSize: true, recursive: true }); - for await (const file of ls.iterator()) { - const type = GitOperations.mode2type(file.mode); - if (type === FileTreeItemType.File) { - yield file; - } - } - } - - public async readTree(git: SimpleGit, oid: string, path: string = '.'): Promise { - const lsTree = new LsTreeSummary(git, oid, path, {}); - const entries = await lsTree.allFiles(); - return { - entries, - oid, - } as Tree; - } - - public static mode2type(mode: string): FileTreeItemType { - switch (mode) { - case '100755': - case '100644': - return FileTreeItemType.File; - case '120000': - return FileTreeItemType.Link; - case '40000': - case '040000': - return FileTreeItemType.Directory; - case '160000': - return FileTreeItemType.Submodule; - default: - throw new Error('unknown mode: ' + mode); - } - } - - /** - * Return a fileTree structure by walking the repo file tree. - * @param uri the repo uri - * @param path the start path - * @param revision the revision - * @param skip pagination parameter, skip how many nodes in each children. - * @param limit pagination parameter, limit the number of node's children. - * @param resolveParents whether the return value should always start from root - * @param flatten - */ - public async fileTree( - uri: RepositoryUri, - path: string, - revision: string = HEAD, - skip: number = 0, - limit: number = DEFAULT_TREE_CHILDREN_LIMIT, - resolveParents: boolean = false, - flatten: boolean = false - ): Promise { - const commit = await this.getCommitOr404(uri, revision); - const git = await this.openGit(uri); - if (path.startsWith('/')) { - path = path.slice(1); - } - if (path.endsWith('/')) { - path = path.slice(0, -1); - } - - function type2item(type: string) { - switch (type) { - case 'blob': - return FileTreeItemType.File; - case 'tree': - return FileTreeItemType.Directory; - case 'commit': - return FileTreeItemType.Submodule; - default: - throw new Error(`unsupported file tree item type ${type}`); - } - } - - if (resolveParents) { - const root: FileTree = { - name: '', - path: '', - type: FileTreeItemType.Directory, - }; - const tree = await this.readTree(git, commit.treeId); - await this.fillChildren(git, root, tree, { skip, limit, flatten }); - if (path) { - await this.resolvePath(git, root, tree, path.split('/'), { skip, limit, flatten }); - } - return root; - } else { - if (path) { - const file = (await this.readTree(git, commit.id, path)).entries[0]; - const result: FileTree = { - name: path.split('/').pop() || '', - path, - type: type2item(file.type!), - sha1: file.id, - }; - if (file.type === 'tree') { - await this.fillChildren(git, result, await this.readTree(git, file.id), { - skip, - limit, - flatten, - }); - } - return result; - } else { - const root: FileTree = { - name: '', - path: '', - type: FileTreeItemType.Directory, - }; - const tree = await this.readTree(git, commit.id, '.'); - await this.fillChildren(git, root, tree, { skip, limit, flatten }); - return root; - } - } - } - - private async fillChildren( - git: SimpleGit, - result: FileTree, - { entries }: Tree, - { skip, limit, flatten }: { skip: number; limit: number; flatten: boolean } - ) { - result.childrenCount = entries.length; - result.children = []; - for (const e of entries.slice(skip, Math.min(entries.length, skip + limit))) { - const child = entry2Tree(e, result.path); - result.children.push(child); - if (flatten && child.type === FileTreeItemType.Directory) { - const tree = await this.readTree(git, e.id); - if (tree.entries.length === 1) { - await this.fillChildren(git, child, tree, { skip, limit, flatten }); - } - } - } - } - - private async resolvePath( - git: SimpleGit, - result: FileTree, - tree: Tree, - paths: string[], - opt: { skip: number; limit: number; flatten: boolean } - ) { - const [path, ...rest] = paths; - for (const entry of tree.entries) { - if (entry.path === path) { - if (!result.children) { - result.children = []; - } - const child = entry2Tree(entry, result.path); - const idx = result.children.findIndex(i => i.sha1 === entry.id); - if (idx < 0) { - result.children.push(child); - } else { - result.children[idx] = child; - } - if (entry.type === 'tree') { - const subTree = await this.readTree(git, entry.id); - await this.fillChildren(git, child, subTree, opt); - if (rest.length > 0) { - await this.resolvePath(git, child, subTree, rest, opt); - } - } - } - } - } - - public async getCommitDiff(uri: string, revision: string): Promise { - const git = await this.openGit(uri); - const commit = await this.getCommitOr404(uri, revision); - if (!revision.includes('..')) { - revision = `${revision}~1..${revision}`; - } - const diffs = await git.diffSummary([revision]); - - const commitDiff: CommitDiff = { - commit, - additions: diffs.insertions, - deletions: diffs.deletions, - files: [], - }; - for (const d of diffs.files) { - if (!d.binary) { - const diff = d as DiffResultTextFile; - const kind = this.diffKind(diff); - switch (kind) { - case DiffKind.ADDED: - { - const path = diff.file; - const modifiedCode = await this.getModifiedCode(git, commit, path); - const language = await detectLanguage(path, modifiedCode); - commitDiff.files.push({ - language, - path, - modifiedCode, - additions: diff.insertions, - deletions: diff.deletions, - kind, - }); - } - break; - case DiffKind.DELETED: - { - const path = diff.file; - const originCode = await this.getOriginCode(git, commit, path); - const language = await detectLanguage(path, originCode); - commitDiff.files.push({ - language, - path, - originCode, - kind, - additions: diff.insertions, - deletions: diff.deletions, - }); - } - break; - case DiffKind.MODIFIED: - { - const path = diff.rename || diff.file; - const modifiedCode = await this.getModifiedCode(git, commit, path); - const originPath = diff.file; - const originCode = await this.getOriginCode(git, commit, originPath); - const language = await detectLanguage(path, modifiedCode); - commitDiff.files.push({ - language, - path, - originPath, - originCode, - modifiedCode, - kind, - additions: diff.insertions, - deletions: diff.deletions, - }); - } - break; - case DiffKind.RENAMED: - { - const path = diff.rename || diff.file; - commitDiff.files.push({ - path, - originPath: diff.file, - kind, - additions: diff.insertions, - deletions: diff.deletions, - }); - } - break; - } - } - } - return commitDiff; - } - - public async getDiff(uri: string, oldRevision: string, newRevision: string): Promise { - const git = await this.openGit(uri); - const diff = await git.diffSummary([oldRevision, newRevision]); - const res: Diff = { - additions: diff.insertions, - deletions: diff.deletions, - files: [], - }; - diff.files.forEach(d => { - if (!d.binary) { - const td = d as DiffResultTextFile; - const kind = this.diffKind(td); - res.files.push({ - path: d.file, - additions: td.insertions, - deletions: td.deletions, - kind, - }); - } - }); - - return res; - } - - private diffKind(diff: DiffResultTextFile) { - let kind: DiffKind = DiffKind.MODIFIED; - if (diff.changes === diff.insertions) { - kind = DiffKind.ADDED; - } else if (diff.changes === diff.deletions) { - kind = DiffKind.DELETED; - } else if (diff.rename) { - kind = DiffKind.RENAMED; - } - return kind; - } - - private async getOriginCode(git: SimpleGit, commit: CommitInfo, path: string) { - const buffer: Buffer = await git.binaryCatFile(['blob', `${commit.id}~1:${path}`]); - return buffer.toString('utf8'); - } - - private async getModifiedCode(git: SimpleGit, commit: CommitInfo, path: string) { - const buffer: Buffer = await git.binaryCatFile(['blob', `${commit.id}:${path}`]); - return buffer.toString('utf8'); - } - - public async getBranchAndTags(repoUri: string): Promise { - const format = { - name: '%(refname:short)', - reference: '%(refname)', - type: '%(objecttype)', - commit: { - updated: '%(*authordate)', - message: '%(*contents)', - committer: '%(*committername)', - author: '%(*authorname)', - id: '%(*objectname)', - parents: '%(*parent)', - treeId: '%(*tree)', - }, - }; - const parser = new FormatParser(format); - const git = await this.openGit(repoUri); - const result = await git.raw([ - 'for-each-ref', - '--format=' + parser.toFormatStr(), - 'refs/tags/*', - 'refs/remotes/origin/*', - ]); - const results = parser.parseResult(result); - return results.map(r => { - const ref: ReferenceInfo = { - name: r.name.startsWith('origin/') ? r.name.slice(7) : r.name, - reference: r.reference, - type: r.type === 'tag' ? ReferenceType.TAG : ReferenceType.REMOTE_BRANCH, - }; - if (r.commit && r.commit.id) { - const commit = { - ...r.commit, - }; - commit.parents = r.commit.parents ? r.commit.parents.split(' ') : []; - commit.updated = new Date(r.commit.updated); - ref.commit = commit; - } - return ref; - }); - } - - public async getCommitOr404(repoUri: string, ref: string): Promise { - const commit = await this.getCommitInfo(repoUri, ref); - if (!commit) { - throw Boom.notFound(`repo ${repoUri} or ${ref} not found`); - } - return commit; - } - - public async log( - repoUri: string, - revision: string, - count: number, - path?: string - ): Promise { - const git = await this.openGit(repoUri); - const options: any = { - n: count, - format: { - updated: '%aI', - message: '%B', - author: '%an', - authorEmail: '%ae', - committer: '%cn', - committerEmail: '%ce', - id: '%H', - parents: '%p', - treeId: '%T', - }, - from: revision, - }; - if (path) { - options.file = path; - } - const result = await git.log(options); - return (result.all as unknown) as CommitInfo[]; - } - - public async resolveRef(repoUri: string, ref: string): Promise { - const git = await this.openGit(repoUri); - let oid = ''; - - try { - // try local branches or tags - oid = (await git.revparse(['-q', '--verify', ref])).trim(); - } catch (e) { - // try remote branches - } - if (!oid) { - try { - oid = (await git.revparse(['-q', '--verify', `origin/${ref}`])).trim(); - } catch (e1) { - // no match - } - } - return oid || null; - } - - public async getCommitInfo(repoUri: string, ref: string): Promise { - const oid = await this.resolveRef(repoUri, ref); - if (oid) { - const commits = await this.log(repoUri, oid, 1); - if (commits.length > 0) { - return commits[0]; - } - } - return null; - } -} diff --git a/x-pack/legacy/plugins/code/server/index.ts b/x-pack/legacy/plugins/code/server/index.ts deleted file mode 100644 index 61ed0c0c5a58c..0000000000000 --- a/x-pack/legacy/plugins/code/server/index.ts +++ /dev/null @@ -1,14 +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 * as constants from '../common/constants'; -import { CodePlugin } from './plugin'; -import { PluginSetupContract } from '../../../../plugins/code/server/index'; - -export const codePlugin = (initializerContext: PluginSetupContract) => - new CodePlugin(initializerContext); - -export { constants }; diff --git a/x-pack/legacy/plugins/code/server/indexer/abstract_indexer.ts b/x-pack/legacy/plugins/code/server/indexer/abstract_indexer.ts deleted file mode 100644 index 66223b7abb529..0000000000000 --- a/x-pack/legacy/plugins/code/server/indexer/abstract_indexer.ts +++ /dev/null @@ -1,201 +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 moment from 'moment'; - -import { Indexer, ProgressReporter } from '.'; -import { - IndexProgress, - IndexRequest, - IndexStats, - IndexStatsKey, - IndexerType, - RepositoryUri, -} from '../../model'; -import { EsClient } from '../lib/esqueue'; -import { Logger } from '../log'; -import { aggregateIndexStats } from '../utils/index_stats_aggregator'; -import { IndexCreationRequest } from './index_creation_request'; -import { IndexCreator } from './index_creator'; - -export abstract class AbstractIndexer implements Indexer { - public type: IndexerType = IndexerType.UNKNOWN; - protected cancelled: boolean = false; - protected indexCreator: IndexCreator; - protected INDEXER_PROGRESS_UPDATE_INTERVAL_MS = 1000; - - constructor( - protected readonly repoUri: RepositoryUri, - protected readonly revision: string, - protected readonly client: EsClient, - protected log: Logger - ) { - this.log = log.addTags(['indexer', this.type]); - this.indexCreator = new IndexCreator(client); - } - - public async start(progressReporter?: ProgressReporter, checkpointReq?: IndexRequest) { - this.log.info( - `Indexer ${this.type} started for repo ${this.repoUri} with revision ${this.revision}` - ); - const isCheckpointValid = this.validateCheckpoint(checkpointReq); - - if (this.needRefreshIndices(checkpointReq)) { - // Prepare the ES index - const res = await this.prepareIndex(); - if (!res) { - this.log.error(`Prepare index for ${this.repoUri} error. Skip indexing.`); - return new Map(); - } - - // Clean up the index if necessary - await this.cleanIndex(); - } - - // Prepare all the index requests - let totalCount = 0; - let prevTimestamp = moment(0); - let successCount = 0; - let failCount = 0; - const statsBuffer: IndexStats[] = []; - - try { - totalCount = await this.getIndexRequestCount(); - } catch (error) { - this.log.error(`Get index request count for ${this.repoUri} error.`); - this.log.error(error); - throw error; - } - - let meetCheckpoint = false; - const reqsIterator = await this.getIndexRequestIterator(); - for await (const req of reqsIterator) { - if (this.isCancelled()) { - this.log.info(`Indexer cancelled. Stop right now.`); - break; - } - - // If checkpoint is valid and has not been met - if (isCheckpointValid && !meetCheckpoint) { - meetCheckpoint = meetCheckpoint || this.ifCheckpointMet(req, checkpointReq!); - if (!meetCheckpoint) { - // If the checkpoint has not been met yet, skip current request. - continue; - } else { - this.log.info(`Checkpoint met. Continue with indexing.`); - } - } - - try { - const stats = await this.processRequest(req); - statsBuffer.push(stats); - successCount += 1; - } catch (error) { - this.log.error(`Process index request error. ${error}`); - failCount += 1; - } - - // Double check if the the indexer is cancelled or not, because the - // processRequest process could take fairly long and during this time - // the index job might have been cancelled already. In this case, - // we shall not update the progress. - if (!this.isCancelled() && progressReporter) { - this.log.debug(`Update progress for ${this.type} indexer.`); - // Update progress if progress reporter has been provided. - const progress: IndexProgress = { - type: this.type, - total: totalCount, - success: successCount, - fail: failCount, - percentage: Math.floor((100 * (successCount + failCount)) / totalCount), - checkpoint: req, - }; - if ( - moment().diff(prevTimestamp) > this.INDEXER_PROGRESS_UPDATE_INTERVAL_MS || - // Ensure that the progress reporter always executed at the end of the job. - successCount + failCount === totalCount - ) { - progressReporter(progress); - prevTimestamp = moment(); - } - } - } - return aggregateIndexStats(statsBuffer); - } - - public cancel() { - this.cancelled = true; - } - - protected isCancelled(): boolean { - return this.cancelled; - } - - // If the current checkpoint is valid - protected validateCheckpoint(checkpointReq?: IndexRequest): boolean { - return checkpointReq !== undefined; - } - - // If it's necessary to refresh (create and reset) all the related indices - protected needRefreshIndices(checkpointReq?: IndexRequest): boolean { - return false; - } - - protected ifCheckpointMet(req: IndexRequest, checkpointReq: IndexRequest): boolean { - // Please override this function - return false; - } - - protected async cleanIndex(): Promise { - // This is the abstract implementation. You should override this. - return new Promise((resolve, reject) => { - resolve(); - }); - } - - protected async *getIndexRequestIterator(): AsyncIterableIterator { - // This is the abstract implementation. You should override this. - } - - protected async getIndexRequestCount(): Promise { - // This is the abstract implementation. You should override this. - return new Promise((resolve, reject) => { - resolve(); - }); - } - - protected async processRequest(request: IndexRequest): Promise { - // This is the abstract implementation. You should override this. - return new Promise((resolve, reject) => { - resolve(); - }); - } - - protected async prepareIndexCreationRequests(): Promise { - // This is the abstract implementation. You should override this. - return new Promise((resolve, reject) => { - resolve(); - }); - } - - protected async prepareIndex() { - const creationReqs = await this.prepareIndexCreationRequests(); - for (const req of creationReqs) { - try { - const res = await this.indexCreator.createIndex(req); - if (!res) { - this.log.info(`Index creation failed for ${req.index}.`); - return false; - } - } catch (error) { - this.log.error(`Index creation error.`); - this.log.error(error); - return false; - } - } - return true; - } -} diff --git a/x-pack/legacy/plugins/code/server/indexer/batch_index_helper.test.ts b/x-pack/legacy/plugins/code/server/indexer/batch_index_helper.test.ts deleted file mode 100644 index 9ba3f61012669..0000000000000 --- a/x-pack/legacy/plugins/code/server/indexer/batch_index_helper.test.ts +++ /dev/null @@ -1,71 +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 sinon from 'sinon'; - -import { EsClient } from '../lib/esqueue'; -import { Logger } from '../log'; -import { emptyAsyncFunc } from '../test_utils'; -import { ConsoleLoggerFactory } from '../utils/console_logger_factory'; -import { BatchIndexHelper } from './batch_index_helper'; - -const log: Logger = new ConsoleLoggerFactory().getLogger(['test']); - -const BATCH_INDEX_SIZE = 5; - -const esClient = { - bulk: emptyAsyncFunc, -}; - -afterEach(() => { - sinon.restore(); -}); - -test('Execute bulk index.', async () => { - // Setup the bulk stub - const bulkSpy = sinon.spy(); - esClient.bulk = bulkSpy; - - const batchIndexHelper = new BatchIndexHelper(esClient as EsClient, log, BATCH_INDEX_SIZE); - - // Submit index requests as many as 2 batches. - for (let i = 0; i < BATCH_INDEX_SIZE * 2; i++) { - await batchIndexHelper.index('mockindex', {}); - } - - expect(bulkSpy.calledTwice).toBeTruthy(); -}); - -test('Do not execute bulk index without enough requests.', async () => { - // Setup the bulk stub - const bulkSpy = sinon.spy(); - esClient.bulk = bulkSpy; - - const batchIndexHelper = new BatchIndexHelper(esClient as EsClient, log, BATCH_INDEX_SIZE); - - // Submit index requests less than one batch. - for (let i = 0; i < BATCH_INDEX_SIZE - 1; i++) { - await batchIndexHelper.index('mockindex', {}); - } - - expect(bulkSpy.notCalled).toBeTruthy(); -}); - -test('Skip bulk index if cancelled.', async () => { - // Setup the bulk stub - const bulkSpy = sinon.spy(); - esClient.bulk = bulkSpy; - - const batchIndexHelper = new BatchIndexHelper(esClient as EsClient, log, BATCH_INDEX_SIZE); - batchIndexHelper.cancel(); - - // Submit index requests more than one batch. - for (let i = 0; i < BATCH_INDEX_SIZE + 1; i++) { - await batchIndexHelper.index('mockindex', {}); - } - - expect(bulkSpy.notCalled).toBeTruthy(); -}); diff --git a/x-pack/legacy/plugins/code/server/indexer/batch_index_helper.ts b/x-pack/legacy/plugins/code/server/indexer/batch_index_helper.ts deleted file mode 100644 index 807690d4ed7ce..0000000000000 --- a/x-pack/legacy/plugins/code/server/indexer/batch_index_helper.ts +++ /dev/null @@ -1,71 +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 { EsClient } from '../lib/esqueue'; -import { Logger } from '../log'; - -/* - * This BatchIndexHelper acts as the index request cache for elasticsearch - * bulk index API. - */ -export class BatchIndexHelper { - public static DEFAULT_BATCH_SIZE = 1000; - private batch: any[]; - private cancelled: boolean = false; - - constructor( - protected readonly client: EsClient, - protected readonly log: Logger, - private batchSize: number = BatchIndexHelper.DEFAULT_BATCH_SIZE - ) { - this.batch = []; - } - - public async index(index: string, body: any) { - if (this.isCancelled()) { - this.log.debug(`Batch index helper is cancelled. Skip.`); - return; - } - this.batch.push({ - index: { - _index: index, - }, - }); - this.batch.push(body); - if (this.batch.length >= this.batchSize * 2) { - return await this.flush(); - } - } - - public async flush() { - if (this.batch.length === 0) { - this.log.debug(`0 index requests found. Skip.`); - return; - } - if (this.isCancelled()) { - this.log.debug(`Batch index helper is cancelled. Skip.`); - return; - } - try { - this.log.debug(`Batch indexed ${this.batch.length / 2} documents.`); - return await this.client.bulk({ body: this.batch }); - } catch (error) { - // TODO(mengwei): should we throw this exception again? - this.log.error(`Batch index ${this.batch.length / 2} documents error. Skip.`); - this.log.error(error); - } finally { - this.batch = []; - } - } - - public isCancelled() { - return this.cancelled; - } - - public cancel() { - this.cancelled = true; - } -} diff --git a/x-pack/legacy/plugins/code/server/indexer/commit_indexer.ts b/x-pack/legacy/plugins/code/server/indexer/commit_indexer.ts deleted file mode 100644 index 3399e5238cb81..0000000000000 --- a/x-pack/legacy/plugins/code/server/indexer/commit_indexer.ts +++ /dev/null @@ -1,161 +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 { ProgressReporter } from '.'; -import { - Commit, - CommitIndexRequest, - IndexStats, - IndexStatsKey, - IndexerType, - RepositoryUri, -} from '../../model'; -import { GitOperations, HEAD } from '../git_operations'; -import { EsClient } from '../lib/esqueue'; -import { Logger } from '../log'; -import { AbstractIndexer } from './abstract_indexer'; -import { BatchIndexHelper } from './batch_index_helper'; -import { getCommitIndexCreationRequest } from './index_creation_request'; -import { CommitIndexName } from './schema'; - -// TODO: implement an incremental commit indexer. -export class CommitIndexer extends AbstractIndexer { - public type: IndexerType = IndexerType.COMMIT; - // Batch index helper for commits - protected commitBatchIndexHelper: BatchIndexHelper; - - private COMMIT_BATCH_INDEX_SIZE = 1000; - - constructor( - protected readonly repoUri: RepositoryUri, - protected readonly revision: string, - protected readonly gitOps: GitOperations, - protected readonly client: EsClient, - protected log: Logger - ) { - super(repoUri, revision, client, log); - this.commitBatchIndexHelper = new BatchIndexHelper(client, log, this.COMMIT_BATCH_INDEX_SIZE); - } - - public async start(progressReporter?: ProgressReporter, checkpointReq?: CommitIndexRequest) { - try { - return await super.start(progressReporter, checkpointReq); - } finally { - if (!this.isCancelled()) { - // Flush all the index request still in the cache for bulk index. - this.commitBatchIndexHelper.flush(); - } - } - } - - public cancel() { - this.commitBatchIndexHelper.cancel(); - super.cancel(); - } - - // If the current checkpoint is valid - protected validateCheckpoint(checkpointReq?: CommitIndexRequest): boolean { - // Up to change when integrate with the actual git api. - return checkpointReq !== undefined && checkpointReq.revision === this.revision; - } - - // If it's necessary to refresh (create and reset) all the related indices - protected needRefreshIndices(checkpointReq?: CommitIndexRequest): boolean { - // If it's not resumed from a checkpoint, then try to refresh all the indices. - return !this.validateCheckpoint(checkpointReq); - } - - protected ifCheckpointMet(req: CommitIndexRequest, checkpointReq: CommitIndexRequest): boolean { - // Assume for the same revision, the order of the files we iterate the repository is definite - // everytime. This is up to change when integrate with the actual git api. - return req.commit.id === checkpointReq.commit.id; - } - - protected async prepareIndexCreationRequests() { - return [getCommitIndexCreationRequest(this.repoUri)]; - } - - protected async *getIndexRequestIterator(): AsyncIterableIterator { - if (!this.commits) { - return; - } - try { - for await (const commit of this.commits) { - const req: CommitIndexRequest = { - repoUri: this.repoUri, - revision: this.revision, - commit, - }; - yield req; - } - } catch (error) { - this.log.error(`Prepare commit indexing requests error.`); - this.log.error(error); - throw error; - } - } - - private commits: Commit[] | null = null; - protected async getIndexRequestCount(): Promise { - try { - this.commits = (await this.gitOps.log(this.repoUri, HEAD, Number.MAX_SAFE_INTEGER)).map(c => { - const [message, ...body] = c.message.split('\n'); - return { - author: { - name: c.author, - email: c.authorEmail, - }, - committer: { - name: c.committer, - email: c.committer, - }, - message, - parents: c.parents, - date: c.updated, - id: c.id, - body: body.join('\n'), - } as Commit; - }); - return this.commits.length; - } catch (error) { - if (this.isCancelled()) { - this.log.debug(`Indexer ${this.type} got cancelled. Skip get index count error.`); - return 1; - } else { - this.log.error(`Get lsp index requests count error.`); - this.log.error(error); - throw error; - } - } - } - - protected async cleanIndex() { - // Clean up all the commits in the commit index - try { - await this.client.deleteByQuery({ - index: CommitIndexName(this.repoUri), - body: { - query: { - match_all: {}, - }, - }, - }); - this.log.info(`Clean up commits for ${this.repoUri} done.`); - } catch (error) { - this.log.error(`Clean up commits for ${this.repoUri} error.`); - this.log.error(error); - } - } - - protected async processRequest(request: CommitIndexRequest): Promise { - const stats: IndexStats = new Map().set(IndexStatsKey.Commit, 0); - const { repoUri, commit } = request; - - await this.commitBatchIndexHelper.index(CommitIndexName(repoUri), commit); - stats.set(IndexStatsKey.Commit, 1); - return stats; - } -} diff --git a/x-pack/legacy/plugins/code/server/indexer/commit_indexer_factory.ts b/x-pack/legacy/plugins/code/server/indexer/commit_indexer_factory.ts deleted file mode 100644 index 0c63aeec926b2..0000000000000 --- a/x-pack/legacy/plugins/code/server/indexer/commit_indexer_factory.ts +++ /dev/null @@ -1,30 +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 { Indexer, IndexerFactory, CommitIndexer } from '.'; -import { RepositoryUri } from '../../model'; -import { GitOperations } from '../git_operations'; -import { EsClient } from '../lib/esqueue'; -import { Logger } from '../log'; - -export class CommitIndexerFactory implements IndexerFactory { - constructor( - protected readonly gitOps: GitOperations, - protected readonly client: EsClient, - protected readonly log: Logger - ) {} - - public async create( - repoUri: RepositoryUri, - revision: string, - // Apply this param when the incremental indexer has been built. - enforcedReindex: boolean = false - ): Promise { - this.log.info(`Create indexer to index ${repoUri} commits`); - // Create the indexer to index the entire repository. - return new CommitIndexer(repoUri, revision, this.gitOps, this.client, this.log); - } -} diff --git a/x-pack/legacy/plugins/code/server/indexer/es_exception.ts b/x-pack/legacy/plugins/code/server/indexer/es_exception.ts deleted file mode 100644 index e0c3fdf5015cf..0000000000000 --- a/x-pack/legacy/plugins/code/server/indexer/es_exception.ts +++ /dev/null @@ -1,10 +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 enum EsException { - INDEX_NOT_FOUND_EXCEPTION = 'index_not_found_exception', - RESOURCE_ALREADY_EXISTS_EXCEPTION = 'resource_already_exists_exception', -} diff --git a/x-pack/legacy/plugins/code/server/indexer/index.ts b/x-pack/legacy/plugins/code/server/indexer/index.ts deleted file mode 100644 index 5b9933760f7cf..0000000000000 --- a/x-pack/legacy/plugins/code/server/indexer/index.ts +++ /dev/null @@ -1,18 +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 * from './commit_indexer'; -export * from './commit_indexer_factory'; -export * from './lsp_incremental_indexer'; -export * from './lsp_indexer'; -export * from './lsp_indexer_factory'; -export * from './indexer'; -export * from './index_creation_request'; -export * from './index_creator'; -export * from './index_migrator'; -export * from './index_version_controller'; -export * from './repository_index_initializer'; -export * from './repository_index_initializer_factory'; diff --git a/x-pack/legacy/plugins/code/server/indexer/index_creation_request.ts b/x-pack/legacy/plugins/code/server/indexer/index_creation_request.ts deleted file mode 100644 index 032c6e7906693..0000000000000 --- a/x-pack/legacy/plugins/code/server/indexer/index_creation_request.ts +++ /dev/null @@ -1,71 +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 { RepositoryUri } from '../../model'; -import { - CommitIndexName, - CommitSchema, - DocumentAnalysisSettings, - DocumentIndexName, - DocumentSchema, - ReferenceIndexName, - ReferenceSchema, - SymbolAnalysisSettings, - SymbolIndexName, - SymbolSchema, -} from './schema'; - -export interface IndexCreationRequest { - index: string; - settings: any; - schema: any; -} - -export const getDocumentIndexCreationRequest = (repoUri: RepositoryUri): IndexCreationRequest => { - return { - index: DocumentIndexName(repoUri), - settings: { - ...DocumentAnalysisSettings, - number_of_shards: 1, - auto_expand_replicas: '0-1', - }, - schema: DocumentSchema, - }; -}; - -export const getSymbolIndexCreationRequest = (repoUri: RepositoryUri): IndexCreationRequest => { - return { - index: SymbolIndexName(repoUri), - settings: { - ...SymbolAnalysisSettings, - number_of_shards: 1, - auto_expand_replicas: '0-1', - }, - schema: SymbolSchema, - }; -}; - -export const getReferenceIndexCreationRequest = (repoUri: RepositoryUri): IndexCreationRequest => { - return { - index: ReferenceIndexName(repoUri), - settings: { - number_of_shards: 1, - auto_expand_replicas: '0-1', - }, - schema: ReferenceSchema, - }; -}; - -export const getCommitIndexCreationRequest = (repoUri: RepositoryUri): IndexCreationRequest => { - return { - index: CommitIndexName(repoUri), - settings: { - number_of_shards: 1, - auto_expand_replicas: '0-1', - }, - schema: CommitSchema, - }; -}; diff --git a/x-pack/legacy/plugins/code/server/indexer/index_creator.test.ts b/x-pack/legacy/plugins/code/server/indexer/index_creator.test.ts deleted file mode 100644 index 3f89023e0b630..0000000000000 --- a/x-pack/legacy/plugins/code/server/indexer/index_creator.test.ts +++ /dev/null @@ -1,70 +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 sinon from 'sinon'; - -import { EsClient } from '../lib/esqueue'; -import { emptyAsyncFunc } from '../test_utils'; -import { IndexCreationRequest } from './index_creation_request'; -import { IndexCreator } from './index_creator'; - -const esClient = { - indices: { - existsAlias: emptyAsyncFunc, - create: emptyAsyncFunc, - putAlias: emptyAsyncFunc, - }, -}; - -afterEach(() => { - sinon.restore(); -}); - -test('Create Alias and Index', async () => { - // Setup the esClient spies - const existsAliasSpy = sinon.fake.returns(false); - const createSpy = sinon.spy(); - const putAliasSpy = sinon.spy(); - esClient.indices.existsAlias = existsAliasSpy; - esClient.indices.create = createSpy; - esClient.indices.putAlias = putAliasSpy; - - const indexCreator = new IndexCreator((esClient as any) as EsClient); - const req: IndexCreationRequest = { - index: 'mockindex', - settings: {}, - schema: {}, - }; - await indexCreator.createIndex(req); - - expect(existsAliasSpy.calledOnce).toBeTruthy(); - expect(createSpy.calledOnce).toBeTruthy(); - expect(putAliasSpy.calledOnce).toBeTruthy(); - expect(createSpy.calledAfter(existsAliasSpy)).toBeTruthy(); - expect(putAliasSpy.calledAfter(createSpy)).toBeTruthy(); -}); - -test('Skip alias and index creation', async () => { - // Setup the esClient spies - const existsAliasSpy = sinon.fake.returns(true); - const createSpy = sinon.spy(); - const putAliasSpy = sinon.spy(); - esClient.indices.existsAlias = existsAliasSpy; - esClient.indices.create = createSpy; - esClient.indices.putAlias = putAliasSpy; - - const indexCreator = new IndexCreator((esClient as any) as EsClient); - const req: IndexCreationRequest = { - index: 'mockindex', - settings: {}, - schema: {}, - }; - await indexCreator.createIndex(req); - - expect(existsAliasSpy.calledOnce).toBeTruthy(); - expect(createSpy.notCalled).toBeTruthy(); - expect(putAliasSpy.notCalled).toBeTruthy(); -}); diff --git a/x-pack/legacy/plugins/code/server/indexer/index_creator.ts b/x-pack/legacy/plugins/code/server/indexer/index_creator.ts deleted file mode 100644 index 917db9b601e6d..0000000000000 --- a/x-pack/legacy/plugins/code/server/indexer/index_creator.ts +++ /dev/null @@ -1,64 +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 { EsClient } from '../lib/esqueue'; -import { IndexCreationRequest } from './index_creation_request'; -import pkg from './schema/version.json'; - -/* - * This IndexCreator deals with anything with elasticsearch index creation. - */ -export class IndexCreator { - private version: number; - - constructor(private readonly client: EsClient) { - this.version = Number(pkg.codeIndexVersion); - } - - public async createIndex(request: IndexCreationRequest): Promise { - const body = { - settings: request.settings, - mappings: { - // Apply the index version in the reserved _meta field of the index. - _meta: { - version: this.version, - }, - dynamic_templates: [ - { - fieldDefaultNotAnalyzed: { - match: '*', - mapping: { - index: false, - norms: false, - }, - }, - }, - ], - properties: request.schema, - }, - }; - - const exists = await this.client.indices.existsAlias({ - name: request.index, - }); - if (!exists) { - // Create the actual index first with the version as the index suffix number. - await this.client.indices.create({ - index: `${request.index}-${this.version}`, - body, - }); - - // Create the alias to point the index just created. - await this.client.indices.putAlias({ - index: `${request.index}-${this.version}`, - name: request.index, - }); - - return true; - } - return exists; - } -} diff --git a/x-pack/legacy/plugins/code/server/indexer/index_migrator.test.ts b/x-pack/legacy/plugins/code/server/indexer/index_migrator.test.ts deleted file mode 100644 index 2cb977b8cb9c0..0000000000000 --- a/x-pack/legacy/plugins/code/server/indexer/index_migrator.test.ts +++ /dev/null @@ -1,57 +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 sinon from 'sinon'; - -import { EsClient } from '../lib/esqueue'; -import { Logger } from '../log'; -import { emptyAsyncFunc } from '../test_utils'; -import { ConsoleLoggerFactory } from '../utils/console_logger_factory'; -import { IndexCreationRequest } from './index_creation_request'; -import { IndexMigrator } from './index_migrator'; - -const log: Logger = new ConsoleLoggerFactory().getLogger(['test']); - -const esClient = { - reindex: emptyAsyncFunc, - indices: { - create: emptyAsyncFunc, - updateAliases: emptyAsyncFunc, - delete: emptyAsyncFunc, - }, -}; - -afterEach(() => { - sinon.restore(); -}); - -test('Normal index migration steps.', async () => { - // Setup the esClient spies - const updateAliasesSpy = sinon.spy(); - const createSpy = sinon.spy(); - const deleteSpy = sinon.spy(); - const reindexSpy = sinon.spy(); - esClient.indices.updateAliases = updateAliasesSpy; - esClient.indices.create = createSpy; - esClient.indices.delete = deleteSpy; - esClient.reindex = reindexSpy; - - const migrator = new IndexMigrator(esClient as EsClient, log); - const req: IndexCreationRequest = { - index: 'mockindex', - settings: {}, - schema: {}, - }; - await migrator.migrateIndex('mockoldindex', req); - - expect(createSpy.calledOnce).toBeTruthy(); - expect(reindexSpy.calledOnce).toBeTruthy(); - expect(updateAliasesSpy.calledOnce).toBeTruthy(); - expect(deleteSpy.calledOnce).toBeTruthy(); - expect(reindexSpy.calledAfter(createSpy)).toBeTruthy(); - expect(updateAliasesSpy.calledAfter(reindexSpy)).toBeTruthy(); - expect(deleteSpy.calledAfter(updateAliasesSpy)).toBeTruthy(); -}); diff --git a/x-pack/legacy/plugins/code/server/indexer/index_migrator.ts b/x-pack/legacy/plugins/code/server/indexer/index_migrator.ts deleted file mode 100644 index a3a8c4d5cbd72..0000000000000 --- a/x-pack/legacy/plugins/code/server/indexer/index_migrator.ts +++ /dev/null @@ -1,156 +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 { - getDocumentIndexCreationRequest, - getReferenceIndexCreationRequest, - getSymbolIndexCreationRequest, - IndexCreationRequest, - IndexVersionController, -} from '.'; -import { Repository } from '../../model'; -import { EsClient } from '../lib/esqueue'; -import { Logger } from '../log'; -import { RepositoryObjectClient } from '../search'; -import { EsException } from './es_exception'; -import pkg from './schema/version.json'; - -export class IndexMigrator { - private version: number; - - constructor(private readonly client: EsClient, private readonly log: Logger) { - this.version = Number(pkg.codeIndexVersion); - } - - public async migrateIndex(oldIndexName: string, request: IndexCreationRequest) { - const body = { - settings: request.settings, - mappings: { - // Apply the index version in the reserved _meta field of the index. - _meta: { - version: this.version, - }, - dynamic_templates: [ - { - fieldDefaultNotAnalyzed: { - match: '*', - mapping: { - index: false, - norms: false, - }, - }, - }, - ], - properties: request.schema, - }, - }; - - const newIndexName = `${request.index}-${this.version}`; - - try { - try { - // Create the new index first with the version as the index suffix number. - await this.client.indices.create({ - index: newIndexName, - body, - }); - } catch (error) { - if ( - error.body && - error.body.error && - error.body.error.type === EsException.RESOURCE_ALREADY_EXISTS_EXCEPTION - ) { - this.log.debug(`The target verion index already exists. Skip index migration`); - return; - } else { - this.log.error(`Create new index ${newIndexName} for index migration error.`); - this.log.error(error); - throw error; - } - } - - try { - // Issue the reindex request for import the data from the old index. - await this.client.reindex({ - body: { - source: { - index: oldIndexName, - }, - dest: { - index: newIndexName, - }, - }, - }); - } catch (error) { - this.log.error( - `Migrate data from ${oldIndexName} to ${newIndexName} for index migration error.` - ); - this.log.error(error); - throw error; - } - - try { - // Update the alias - await this.client.indices.updateAliases({ - body: { - actions: [ - { - remove: { - index: oldIndexName, - alias: request.index, - }, - }, - { - add: { - index: newIndexName, - alias: request.index, - }, - }, - ], - }, - }); - } catch (error) { - this.log.error(`Update the index alias for ${newIndexName} error.`); - this.log.error(error); - throw error; - } - - try { - // Delete the old index - await this.client.indices.delete({ index: oldIndexName }); - } catch (error) { - this.log.error(`Clean up the old index ${oldIndexName} error.`); - this.log.error(error); - // This won't affect serving, so do not throw the error anymore. - } - } catch (error) { - this.log.error(`Index upgrade/migration to version ${this.version} failed.`); - this.log.error(error); - } - } -} - -export const tryMigrateIndices = async (client: EsClient, log: Logger) => { - log.info('Check the versions of Code indices...'); - const repoObjectClient = new RepositoryObjectClient(client); - const repos: Repository[] = await repoObjectClient.getAllRepositories(); - - const migrationPromises = []; - for (const repo of repos) { - const docIndexVersionController = new IndexVersionController(client, log); - const docCreationReq = getDocumentIndexCreationRequest(repo.uri); - migrationPromises.push(docIndexVersionController.tryUpgrade(docCreationReq)); - - const symbolIndexVersionController = new IndexVersionController(client, log); - const symbolCreationReq = getSymbolIndexCreationRequest(repo.uri); - migrationPromises.push(symbolIndexVersionController.tryUpgrade(symbolCreationReq)); - - const refIndexVersionController = new IndexVersionController(client, log); - const refCreationReq = getReferenceIndexCreationRequest(repo.uri); - migrationPromises.push(refIndexVersionController.tryUpgrade(refCreationReq)); - } - return Promise.all(migrationPromises); -}; diff --git a/x-pack/legacy/plugins/code/server/indexer/index_version_controller.test.ts b/x-pack/legacy/plugins/code/server/indexer/index_version_controller.test.ts deleted file mode 100644 index 57724d9e47dcb..0000000000000 --- a/x-pack/legacy/plugins/code/server/indexer/index_version_controller.test.ts +++ /dev/null @@ -1,111 +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 sinon from 'sinon'; - -import { EsClient } from '../lib/esqueue'; -import { Logger } from '../log'; -import { emptyAsyncFunc } from '../test_utils'; -import { ConsoleLoggerFactory } from '../utils/console_logger_factory'; -import { IndexCreationRequest } from './index_creation_request'; -import { IndexVersionController } from './index_version_controller'; -import pkg from './schema/version.json'; - -const log: Logger = new ConsoleLoggerFactory().getLogger(['test']); - -const esClient = { - reindex: emptyAsyncFunc, - indices: { - getMapping: emptyAsyncFunc, - create: emptyAsyncFunc, - updateAliases: emptyAsyncFunc, - delete: emptyAsyncFunc, - }, -}; - -afterEach(() => { - sinon.restore(); -}); - -test('Index upgrade is triggered.', async () => { - // Setup the esClient spies - const getMappingSpy = sinon.fake.returns( - Promise.resolve({ - mockindex: { - mappings: { - _meta: { - version: 0, - }, - }, - }, - }) - ); - const updateAliasesSpy = sinon.spy(); - const createSpy = sinon.spy(); - const deleteSpy = sinon.spy(); - const reindexSpy = sinon.spy(); - esClient.indices.getMapping = getMappingSpy; - esClient.indices.updateAliases = updateAliasesSpy; - esClient.indices.create = createSpy; - esClient.indices.delete = deleteSpy; - esClient.reindex = reindexSpy; - - const versionController = new IndexVersionController(esClient as EsClient, log); - const req: IndexCreationRequest = { - index: 'mockindex', - settings: {}, - schema: {}, - }; - await versionController.tryUpgrade(req); - - expect(getMappingSpy.calledOnce).toBeTruthy(); - expect(createSpy.calledOnce).toBeTruthy(); - expect(reindexSpy.calledOnce).toBeTruthy(); - expect(updateAliasesSpy.calledOnce).toBeTruthy(); - expect(deleteSpy.calledOnce).toBeTruthy(); - expect(createSpy.calledAfter(getMappingSpy)).toBeTruthy(); - expect(reindexSpy.calledAfter(getMappingSpy)).toBeTruthy(); - expect(updateAliasesSpy.calledAfter(getMappingSpy)).toBeTruthy(); - expect(deleteSpy.calledAfter(getMappingSpy)).toBeTruthy(); -}); - -test('Index upgrade is skipped.', async () => { - // Setup the esClient spies - const getMappingSpy = sinon.fake.returns( - Promise.resolve({ - mockindex: { - mappings: { - _meta: { - version: pkg.codeIndexVersion, - }, - }, - }, - }) - ); - const updateAliasesSpy = sinon.spy(); - const createSpy = sinon.spy(); - const deleteSpy = sinon.spy(); - const reindexSpy = sinon.spy(); - esClient.indices.getMapping = getMappingSpy; - esClient.indices.updateAliases = updateAliasesSpy; - esClient.indices.create = createSpy; - esClient.indices.delete = deleteSpy; - esClient.reindex = reindexSpy; - - const versionController = new IndexVersionController(esClient as EsClient, log); - const req: IndexCreationRequest = { - index: 'mockindex', - settings: {}, - schema: {}, - }; - await versionController.tryUpgrade(req); - - expect(getMappingSpy.calledOnce).toBeTruthy(); - expect(createSpy.notCalled).toBeTruthy(); - expect(reindexSpy.notCalled).toBeTruthy(); - expect(updateAliasesSpy.notCalled).toBeTruthy(); - expect(deleteSpy.notCalled).toBeTruthy(); -}); diff --git a/x-pack/legacy/plugins/code/server/indexer/index_version_controller.ts b/x-pack/legacy/plugins/code/server/indexer/index_version_controller.ts deleted file mode 100644 index e753b9f97b1c4..0000000000000 --- a/x-pack/legacy/plugins/code/server/indexer/index_version_controller.ts +++ /dev/null @@ -1,72 +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 _ from 'lodash'; - -import { IndexMigrator } from '.'; -import { EsClient } from '../lib/esqueue'; -import { Logger } from '../log'; -import { EsException } from './es_exception'; -import { IndexCreationRequest } from './index_creation_request'; -import pkg from './schema/version.json'; - -export class IndexVersionController { - private version: number; - private DEFAULT_VERSION = 0; - - constructor(protected readonly client: EsClient, private readonly log: Logger) { - this.version = Number(pkg.codeIndexVersion); - } - - public async tryUpgrade(request: IndexCreationRequest) { - this.log.debug(`Try upgrade index mapping/settings for index ${request.index}.`); - try { - const esIndexVersion = await this.getIndexVersionFromES(request.index); - const needUpgrade = this.needUpgrade(esIndexVersion); - if (needUpgrade) { - const migrator = new IndexMigrator(this.client, this.log); - const oldIndexName = `${request.index}-${esIndexVersion}`; - this.log.info( - `Migrate index mapping/settings from version ${esIndexVersion} for ${request.index}` - ); - return migrator.migrateIndex(oldIndexName, request); - } else { - this.log.debug(`Index version is update-to-date for ${request.index}`); - } - } catch (error) { - if ( - error.body && - error.body.error && - error.body.error.type === EsException.INDEX_NOT_FOUND_EXCEPTION - ) { - this.log.info(`Skip upgrade index ${request.index} because original index does not exist.`); - } else { - this.log.error(`Try upgrade index error for ${request.index}.`); - this.log.error(error); - } - } - } - - /* - * Currently there is a simple rule to decide if we need upgrade the index or not: if the index - * version is smaller than current version specified in the package.json file under `codeIndexVersion`. - */ - protected needUpgrade(oldIndexVersion: number): boolean { - return oldIndexVersion < this.version; - } - - private async getIndexVersionFromES(indexName: string): Promise { - const res = await this.client.indices.getMapping({ - index: indexName, - }); - const esIndexName = Object.keys(res)[0]; - const version = _.get(res, [esIndexName, 'mappings', '_meta', 'version'], this.DEFAULT_VERSION); - if (version === this.DEFAULT_VERSION) { - this.log.warn(`Can't find index version field in _meta for ${indexName}.`); - } - return version; - } -} diff --git a/x-pack/legacy/plugins/code/server/indexer/indexer.ts b/x-pack/legacy/plugins/code/server/indexer/indexer.ts deleted file mode 100644 index f235bf9eaee63..0000000000000 --- a/x-pack/legacy/plugins/code/server/indexer/indexer.ts +++ /dev/null @@ -1,23 +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 { IndexProgress, IndexRequest, IndexStats, IndexerType, RepositoryUri } from '../../model'; - -export type ProgressReporter = (progress: IndexProgress) => void; - -export interface Indexer { - type: IndexerType; - start(ProgressReporter?: ProgressReporter, checkpointReq?: IndexRequest): Promise; - cancel(): void; -} - -export interface IndexerFactory { - create( - repoUri: RepositoryUri, - revision: string, - enforcedReindex: boolean - ): Promise; -} diff --git a/x-pack/legacy/plugins/code/server/indexer/lsp_incremental_indexer.ts b/x-pack/legacy/plugins/code/server/indexer/lsp_incremental_indexer.ts deleted file mode 100644 index 9516150903e8e..0000000000000 --- a/x-pack/legacy/plugins/code/server/indexer/lsp_incremental_indexer.ts +++ /dev/null @@ -1,261 +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 { ProgressReporter } from '.'; -import { Diff, DiffKind } from '../../common/git_diff'; -import { toCanonicalUrl } from '../../common/uri_util'; -import { - Document, - IndexStats, - IndexStatsKey, - IndexerType, - LspIncIndexRequest, - RepositoryUri, -} from '../../model'; -import { GitOperations, HEAD } from '../git_operations'; -import { EsClient } from '../lib/esqueue'; -import { Logger } from '../log'; -import { LspService } from '../lsp/lsp_service'; -import { ServerOptions } from '../server_options'; -import { detectLanguage } from '../utils/detect_language'; -import { LspIndexer } from './lsp_indexer'; -import { DocumentIndexName, SymbolIndexName } from './schema'; - -export class LspIncrementalIndexer extends LspIndexer { - public type = IndexerType.LSP_INC; - private diff: Diff | undefined = undefined; - - constructor( - protected readonly repoUri: RepositoryUri, - // The latest revision to be indexed - protected readonly revision: string, - // The already indexed revision - protected readonly originRevision: string, - protected readonly lspService: LspService, - protected readonly options: ServerOptions, - protected readonly gitOps: GitOperations, - protected readonly client: EsClient, - protected readonly log: Logger - ) { - super(repoUri, revision, lspService, options, gitOps, client, log); - } - - public async start(progressReporter?: ProgressReporter, checkpointReq?: LspIncIndexRequest) { - return await super.start(progressReporter, checkpointReq); - } - - // If the current checkpoint is valid. Otherwise, ignore the checkpoint - protected validateCheckpoint(checkpointReq?: LspIncIndexRequest): boolean { - return ( - checkpointReq !== undefined && - checkpointReq.revision === this.revision && - checkpointReq.originRevision === this.originRevision - ); - } - - // If it's necessary to refresh (create and reset) all the related indices - protected needRefreshIndices(_: LspIncIndexRequest): boolean { - return false; - } - - protected ifCheckpointMet(req: LspIncIndexRequest, checkpointReq: LspIncIndexRequest): boolean { - // Assume for the same revision pair, the order of the files we iterate the diff is definite - // everytime. - return ( - req.filePath === checkpointReq.filePath && - req.revision === checkpointReq.revision && - req.originRevision === checkpointReq.originRevision && - req.kind === checkpointReq.kind - ); - } - - protected async prepareIndexCreationRequests() { - // We don't need to create new indices for incremental indexing. - return []; - } - - protected async processRequest(request: LspIncIndexRequest): Promise { - const stats: IndexStats = new Map() - .set(IndexStatsKey.Symbol, 0) - .set(IndexStatsKey.Reference, 0) - .set(IndexStatsKey.File, 0) - .set(IndexStatsKey.SymbolDeleted, 0) - .set(IndexStatsKey.ReferenceDeleted, 0) - .set(IndexStatsKey.FileDeleted, 0); - if (this.isCancelled()) { - this.log.debug(`Incremental indexer is cancelled. Skip.`); - return stats; - } - - const { kind } = request; - - this.log.debug(`Index ${kind} request ${JSON.stringify(request, null, 2)}`); - switch (kind) { - case DiffKind.ADDED: { - await this.handleAddedRequest(request, stats); - break; - } - case DiffKind.DELETED: { - await this.handleDeletedRequest(request, stats); - break; - } - case DiffKind.MODIFIED: { - await this.handleModifiedRequest(request, stats); - break; - } - case DiffKind.RENAMED: { - await this.handleRenamedRequest(request, stats); - break; - } - default: { - this.log.debug( - `Unsupported diff kind ${kind} for incremental indexing. Skip this request.` - ); - } - } - - return stats; - } - - protected async *getIndexRequestIterator(): AsyncIterableIterator { - try { - if (this.diff) { - await this.prepareWorkspace(); - for (const f of this.diff.files) { - yield { - repoUri: this.repoUri, - filePath: f.path, - originPath: f.originPath, - // Always use HEAD for now until we have multi revision. - // Also, since the workspace might get updated during the index, we always - // want the revision to keep updated so that lsp proxy could pass the revision - // check per discussion here: https://github.com/elastic/code/issues/1317#issuecomment-504615833 - revision: HEAD, - kind: f.kind, - originRevision: this.originRevision, - }; - } - } - } catch (error) { - this.log.error(`Get lsp incremental index requests count error.`); - this.log.error(error); - throw error; - } - } - - protected async getIndexRequestCount(): Promise { - try { - // cache here to avoid pulling the diff twice. - this.diff = await this.gitOps.getDiff(this.repoUri, this.originRevision, this.revision); - return this.diff.files.length; - } catch (error) { - if (this.isCancelled()) { - this.log.debug(`Incremental indexer got cancelled. Skip get index count error.`); - return 1; - } else { - this.log.error(`Get lsp incremental index requests count error.`); - this.log.error(error); - throw error; - } - } - } - - protected async cleanIndex() { - this.log.info('Do not need to clean index for incremental indexing.'); - } - - private async handleAddedRequest(request: LspIncIndexRequest, stats: IndexStats) { - const { repoUri, filePath } = request; - - let content = ''; - try { - content = await this.getFileSource(request); - } catch (error) { - if ((error as Error).message === this.FILE_OVERSIZE_ERROR_MSG) { - // Skip this index request if the file is oversized - this.log.debug(this.FILE_OVERSIZE_ERROR_MSG); - return stats; - } else { - // Rethrow the issue if for other reasons - throw error; - } - } - - const { symbolNames, symbolsLength, referencesLength } = await this.execLspIndexing(request); - stats.set(IndexStatsKey.Symbol, symbolsLength); - stats.set(IndexStatsKey.Reference, referencesLength); - - const language = await detectLanguage(filePath, Buffer.from(content)); - const body: Document = { - repoUri, - path: filePath, - content, - language, - qnames: Array.from(symbolNames), - }; - await this.docBatchIndexHelper.index(DocumentIndexName(repoUri), body); - stats.set(IndexStatsKey.File, 1); - } - - private async handleDeletedRequest(request: LspIncIndexRequest, stats: IndexStats) { - const { revision, filePath, repoUri } = request; - - // Delete the document with the exact file path. TODO: add stats - const docRes = await this.client.deleteByQuery({ - index: DocumentIndexName(repoUri), - body: { - query: { - term: { - 'path.hierarchy': filePath, - }, - }, - }, - }); - if (docRes) { - stats.set(IndexStatsKey.FileDeleted, docRes.deleted); - } - - const lspDocUri = toCanonicalUrl({ repoUri, revision, file: filePath, schema: 'git:' }); - - // Delete all symbols within this file - const symbolRes = await this.client.deleteByQuery({ - index: SymbolIndexName(repoUri), - body: { - query: { - term: { - 'symbolInformation.location.uri': lspDocUri, - }, - }, - }, - }); - if (symbolRes) { - stats.set(IndexStatsKey.SymbolDeleted, symbolRes.deleted); - } - - // TODO: When references is enabled. Clean up the references as well. - } - - private async handleModifiedRequest(request: LspIncIndexRequest, stats: IndexStats) { - const { originRevision, originPath } = request; - - // 1. first delete all related indexed data - await this.handleDeletedRequest( - { - ...request, - revision: originRevision, - filePath: originPath ? originPath : '', - }, - stats - ); - // 2. index data with modified version - await this.handleAddedRequest(request, stats); - } - - private async handleRenamedRequest(request: LspIncIndexRequest, stats: IndexStats) { - // Do the same as modified file - await this.handleModifiedRequest(request, stats); - } -} diff --git a/x-pack/legacy/plugins/code/server/indexer/lsp_indexer.ts b/x-pack/legacy/plugins/code/server/indexer/lsp_indexer.ts deleted file mode 100644 index f1334664d5907..0000000000000 --- a/x-pack/legacy/plugins/code/server/indexer/lsp_indexer.ts +++ /dev/null @@ -1,333 +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 { ResponseError } from 'vscode-jsonrpc'; -import fs from 'fs'; -import path from 'path'; -import { promisify } from 'util'; -import { isBinaryFile } from 'isbinaryfile'; -import { ProgressReporter } from '.'; -import { TEXT_FILE_LIMIT } from '../../common/file'; -import { - LanguageServerNotInstalled, - LanguageServerStartFailed, -} from '../../common/lsp_error_codes'; -import { toCanonicalUrl } from '../../common/uri_util'; -import { - Document, - IndexStats, - IndexStatsKey, - IndexerType, - LspIndexRequest, - RepositoryUri, -} from '../../model'; -import { GitOperations, HEAD } from '../git_operations'; -import { EsClient } from '../lib/esqueue'; -import { Logger } from '../log'; -import { LspService } from '../lsp/lsp_service'; -import { ServerOptions } from '../server_options'; -import { detectLanguage, detectLanguageByFilename } from '../utils/detect_language'; -import { AbstractIndexer } from './abstract_indexer'; -import { BatchIndexHelper } from './batch_index_helper'; -import { - getDocumentIndexCreationRequest, - getReferenceIndexCreationRequest, - getSymbolIndexCreationRequest, -} from './index_creation_request'; -import { ALL_RESERVED, DocumentIndexName, ReferenceIndexName, SymbolIndexName } from './schema'; - -const state = promisify(fs.stat); -const readFile = promisify(fs.readFile); - -export class LspIndexer extends AbstractIndexer { - public type: IndexerType = IndexerType.LSP; - // Batch index helper for symbols/references - protected lspBatchIndexHelper: BatchIndexHelper; - // Batch index helper for documents - protected docBatchIndexHelper: BatchIndexHelper; - - private LSP_BATCH_INDEX_SIZE = 50; - private DOC_BATCH_INDEX_SIZE = 50; - private workspaceDir: string = ''; - - constructor( - protected readonly repoUri: RepositoryUri, - protected readonly revision: string, - protected readonly lspService: LspService, - protected readonly options: ServerOptions, - protected readonly gitOps: GitOperations, - protected readonly client: EsClient, - protected log: Logger - ) { - super(repoUri, revision, client, log); - this.lspBatchIndexHelper = new BatchIndexHelper(client, log, this.LSP_BATCH_INDEX_SIZE); - this.docBatchIndexHelper = new BatchIndexHelper(client, log, this.DOC_BATCH_INDEX_SIZE); - } - - public async start(progressReporter?: ProgressReporter, checkpointReq?: LspIndexRequest) { - try { - return await super.start(progressReporter, checkpointReq); - } finally { - if (!this.isCancelled()) { - // Flush all the index request still in the cache for bulk index. - this.lspBatchIndexHelper.flush(); - this.docBatchIndexHelper.flush(); - } - } - } - - public cancel() { - this.lspBatchIndexHelper.cancel(); - this.docBatchIndexHelper.cancel(); - super.cancel(); - } - - // If the current checkpoint is valid - protected validateCheckpoint(checkpointReq?: LspIndexRequest): boolean { - return checkpointReq !== undefined && checkpointReq.revision === this.revision; - } - - // If it's necessary to refresh (create and reset) all the related indices - protected needRefreshIndices(checkpointReq?: LspIndexRequest): boolean { - // If it's not resumed from a checkpoint, then try to refresh all the indices. - return !this.validateCheckpoint(checkpointReq); - } - - protected ifCheckpointMet(req: LspIndexRequest, checkpointReq: LspIndexRequest): boolean { - // Assume for the same revision, the order of the files we iterate the repository is definite - // everytime. - return req.filePath === checkpointReq.filePath && req.revision === checkpointReq.revision; - } - - protected async prepareIndexCreationRequests() { - return [ - getDocumentIndexCreationRequest(this.repoUri), - getReferenceIndexCreationRequest(this.repoUri), - getSymbolIndexCreationRequest(this.repoUri), - ]; - } - - protected async *getIndexRequestIterator(): AsyncIterableIterator { - try { - await this.prepareWorkspace(); - - const fileIterator = await this.gitOps.iterateRepo(this.repoUri, HEAD); - for await (const file of fileIterator) { - const filePath = file.path!; - const req: LspIndexRequest = { - repoUri: this.repoUri, - filePath, - // Always use HEAD for now until we have multi revision. - // Also, since the workspace might get updated during the index, we always - // want the revision to keep updated so that lsp proxy could pass the revision - // check per discussion here: https://github.com/elastic/code/issues/1317#issuecomment-504615833 - revision: HEAD, - }; - yield req; - } - } catch (error) { - this.log.error(`Prepare ${this.type} indexing requests error.`); - this.log.error(error); - throw error; - } - } - - protected async prepareWorkspace() { - const { workspaceDir } = await this.lspService.workspaceHandler.openWorkspace( - this.repoUri, - HEAD - ); - this.workspaceDir = workspaceDir; - } - - protected async getIndexRequestCount(): Promise { - try { - return await this.gitOps.countRepoFiles(this.repoUri, HEAD); - } catch (error) { - if (this.isCancelled()) { - this.log.debug(`Indexer ${this.type} got cancelled. Skip get index count error.`); - return 1; - } else { - this.log.error(`Get ${this.type} index requests count error.`); - this.log.error(error); - throw error; - } - } - } - - protected async cleanIndex() { - // Clean up all the symbol documents in the symbol index - try { - await this.client.deleteByQuery({ - index: SymbolIndexName(this.repoUri), - body: { - query: { - match_all: {}, - }, - }, - }); - this.log.info(`Clean up symbols for ${this.repoUri} done.`); - } catch (error) { - this.log.error(`Clean up symbols for ${this.repoUri} error.`); - this.log.error(error); - } - - // Clean up all the reference documents in the reference index - try { - await this.client.deleteByQuery({ - index: ReferenceIndexName(this.repoUri), - body: { - query: { - match_all: {}, - }, - }, - }); - this.log.info(`Clean up references for ${this.repoUri} done.`); - } catch (error) { - this.log.error(`Clean up references for ${this.repoUri} error.`); - this.log.error(error); - } - - // Clean up all the document documents in the document index but keep the repository document. - try { - await this.client.deleteByQuery({ - index: DocumentIndexName(this.repoUri), - body: { - query: { - bool: { - must_not: ALL_RESERVED.map((field: string) => ({ - exists: { - field, - }, - })), - }, - }, - }, - }); - this.log.info(`Clean up documents for ${this.repoUri} done.`); - } catch (error) { - this.log.error(`Clean up documents for ${this.repoUri} error.`); - this.log.error(error); - } - } - - protected FILE_OVERSIZE_ERROR_MSG = 'File size exceeds limit. Skip index.'; - protected BINARY_FILE_ERROR_MSG = 'Binary file detected. Skip index.'; - protected async getFileSource(request: LspIndexRequest): Promise { - const { filePath } = request; - const fullPath = path.join(this.workspaceDir, filePath); - const fileStat = await state(fullPath); - const fileSize = fileStat.size; - if (fileSize > TEXT_FILE_LIMIT) { - throw new Error(this.FILE_OVERSIZE_ERROR_MSG); - } - const bin = await isBinaryFile(fullPath); - if (bin) { - throw new Error(this.BINARY_FILE_ERROR_MSG); - } - return readFile(fullPath, { encoding: 'utf8' }) as Promise; - } - - protected async execLspIndexing( - request: LspIndexRequest - ): Promise<{ - symbolNames: Set; - symbolsLength: number; - referencesLength: number; - }> { - const { repoUri, revision, filePath } = request; - const lspDocUri = toCanonicalUrl({ repoUri, revision, file: filePath, schema: 'git:' }); - const symbolNames = new Set(); - let symbolsLength = 0; - let referencesLength = 0; - try { - const lang = detectLanguageByFilename(filePath); - // filter file by language - if (lang && this.lspService.supportLanguage(lang)) { - const response = await this.lspService.sendRequest('textDocument/full', { - textDocument: { - uri: lspDocUri, - }, - reference: this.options.enableGlobalReference, - }); - - if (response && response.result && response.result.length > 0 && response.result[0]) { - const { symbols, references } = response.result[0]; - for (const symbol of symbols) { - await this.lspBatchIndexHelper.index(SymbolIndexName(repoUri), symbol); - symbolNames.add(symbol.symbolInformation.name); - } - symbolsLength = symbols.length; - - for (const ref of references) { - await this.lspBatchIndexHelper.index(ReferenceIndexName(repoUri), ref); - } - referencesLength = references.length; - } else { - this.log.debug(`Empty response from lsp server. Skip symbols and references indexing.`); - } - } else { - this.log.debug(`Unsupported language. Skip symbols and references indexing.`); - } - } catch (error) { - if (error instanceof ResponseError && error.code === LanguageServerNotInstalled) { - // TODO maybe need to report errors to the index task and warn user later - this.log.debug(`Index symbols or references error due to language server not installed`); - } else if (error instanceof ResponseError && error.code === LanguageServerStartFailed) { - this.log.debug( - `Index symbols or references error due to language server can't be started.` - ); - } else { - this.log.warn(`Index symbols or references error.`); - this.log.warn(error); - } - } - - return { symbolNames, symbolsLength, referencesLength }; - } - - protected async processRequest(request: LspIndexRequest): Promise { - const stats: IndexStats = new Map() - .set(IndexStatsKey.Symbol, 0) - .set(IndexStatsKey.Reference, 0) - .set(IndexStatsKey.File, 0); - const { repoUri, revision, filePath } = request; - this.log.debug(`Indexing ${filePath} at revision ${revision} for ${repoUri}`); - - let content = ''; - try { - content = await this.getFileSource(request); - } catch (error) { - if ((error as Error).message === this.BINARY_FILE_ERROR_MSG) { - this.log.debug(this.BINARY_FILE_ERROR_MSG); - return stats; - } else if ((error as Error).message === this.FILE_OVERSIZE_ERROR_MSG) { - // Skip this index request if the file is oversized - this.log.debug(this.FILE_OVERSIZE_ERROR_MSG); - return stats; - } else { - // Rethrow the issue if for other reasons - throw error; - } - } - - const { symbolNames, symbolsLength, referencesLength } = await this.execLspIndexing(request); - stats.set(IndexStatsKey.Symbol, symbolsLength); - stats.set(IndexStatsKey.Reference, referencesLength); - - const language = await detectLanguage(filePath, Buffer.from(content)); - const body: Document = { - repoUri, - path: filePath, - content, - language, - qnames: Array.from(symbolNames), - }; - await this.docBatchIndexHelper.index(DocumentIndexName(repoUri), body); - stats.set(IndexStatsKey.File, 1); - return stats; - } -} diff --git a/x-pack/legacy/plugins/code/server/indexer/lsp_indexer_factory.ts b/x-pack/legacy/plugins/code/server/indexer/lsp_indexer_factory.ts deleted file mode 100644 index d0d5ae4e3e16b..0000000000000 --- a/x-pack/legacy/plugins/code/server/indexer/lsp_indexer_factory.ts +++ /dev/null @@ -1,70 +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 { Indexer, IndexerFactory, LspIncrementalIndexer, LspIndexer } from '.'; -import { RepositoryUri } from '../../model'; -import { GitOperations } from '../git_operations'; -import { EsClient } from '../lib/esqueue'; -import { Logger } from '../log'; -import { LspService } from '../lsp/lsp_service'; -import { RepositoryObjectClient } from '../search'; -import { ServerOptions } from '../server_options'; - -export class LspIndexerFactory implements IndexerFactory { - private objectClient: RepositoryObjectClient; - - constructor( - protected readonly lspService: LspService, - protected readonly options: ServerOptions, - protected readonly gitOps: GitOperations, - protected readonly client: EsClient, - protected readonly log: Logger - ) { - this.objectClient = new RepositoryObjectClient(this.client); - } - - public async create( - repoUri: RepositoryUri, - revision: string, - enforcedReindex: boolean = false - ): Promise { - try { - const repo = await this.objectClient.getRepository(repoUri); - const indexedRevision = repo.indexedRevision; - // Skip incremental indexer if enforced reindex. - if (!enforcedReindex && indexedRevision) { - this.log.info(`Create indexer to index ${repoUri} from ${indexedRevision} to ${revision}`); - // Create the indexer to index only the diff between these 2 revisions. - return new LspIncrementalIndexer( - repo.uri, - revision, - indexedRevision, - this.lspService, - this.options, - this.gitOps, - this.client, - this.log - ); - } else { - this.log.info(`Create indexer to index ${repoUri} at ${revision}`); - // Create the indexer to index the entire repository. - return new LspIndexer( - repo.uri, - revision, - this.lspService, - this.options, - this.gitOps, - this.client, - this.log - ); - } - } catch (error) { - this.log.error(`Create indexer error for ${repoUri}.`); - this.log.error(error); - return undefined; - } - } -} diff --git a/x-pack/legacy/plugins/code/server/indexer/repository_index_initializer.test.ts b/x-pack/legacy/plugins/code/server/indexer/repository_index_initializer.test.ts deleted file mode 100644 index 7ef010451caa3..0000000000000 --- a/x-pack/legacy/plugins/code/server/indexer/repository_index_initializer.test.ts +++ /dev/null @@ -1,52 +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 sinon from 'sinon'; - -import { EsClient } from '../lib/esqueue'; -import { Logger } from '../log'; -import { emptyAsyncFunc } from '../test_utils'; -import { ConsoleLoggerFactory } from '../utils/console_logger_factory'; -import { RepositoryIndexInitializer } from './repository_index_initializer'; - -const log: Logger = new ConsoleLoggerFactory().getLogger(['test']); - -const esClient = { - indices: { - existsAlias: emptyAsyncFunc, - create: emptyAsyncFunc, - putAlias: emptyAsyncFunc, - }, -}; - -afterEach(() => { - sinon.restore(); -}); - -test('Initialize the repository index', async () => { - // Setup the esClient spies - const existsAliasSpy = sinon.fake.returns(false); - const createSpy = sinon.spy(); - const putAliasSpy = sinon.spy(); - esClient.indices.existsAlias = existsAliasSpy; - esClient.indices.create = createSpy; - esClient.indices.putAlias = putAliasSpy; - - const initializer = new RepositoryIndexInitializer( - 'mockuri', - 'mockrevision', - esClient as EsClient, - log - ); - await initializer.init(); - - // Expect these indices functions to be called only once. - expect(existsAliasSpy.calledOnce).toBeTruthy(); - expect(createSpy.calledOnce).toBeTruthy(); - expect(putAliasSpy.calledOnce).toBeTruthy(); - expect(createSpy.calledAfter(existsAliasSpy)).toBeTruthy(); - expect(putAliasSpy.calledAfter(createSpy)).toBeTruthy(); -}); diff --git a/x-pack/legacy/plugins/code/server/indexer/repository_index_initializer.ts b/x-pack/legacy/plugins/code/server/indexer/repository_index_initializer.ts deleted file mode 100644 index 869f26efd8226..0000000000000 --- a/x-pack/legacy/plugins/code/server/indexer/repository_index_initializer.ts +++ /dev/null @@ -1,47 +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 { IndexerType, RepositoryUri } from '../../model'; -import { EsClient } from '../lib/esqueue'; -import { Logger } from '../log'; -import { AbstractIndexer } from './abstract_indexer'; -import { IndexCreationRequest } from './index_creation_request'; -import { RepositoryAnalysisSettings, RepositoryIndexName, RepositorySchema } from './schema'; - -// Inherit AbstractIndexer's index creation logics. This is not an actual indexer. -export class RepositoryIndexInitializer extends AbstractIndexer { - public type: IndexerType = IndexerType.REPOSITORY; - - constructor( - protected readonly repoUri: RepositoryUri, - protected readonly revision: string, - protected readonly client: EsClient, - protected readonly log: Logger - ) { - super(repoUri, revision, client, log); - } - - public async prepareIndexCreationRequests() { - const creationReq: IndexCreationRequest = { - index: RepositoryIndexName(this.repoUri), - settings: { - ...RepositoryAnalysisSettings, - number_of_shards: 1, - auto_expand_replicas: '0-1', - }, - schema: RepositorySchema, - }; - return [creationReq]; - } - - public async init() { - const res = await this.prepareIndex(); - if (!res) { - this.log.error(`Initialize repository index failed.`); - } - return; - } -} diff --git a/x-pack/legacy/plugins/code/server/indexer/repository_index_initializer_factory.ts b/x-pack/legacy/plugins/code/server/indexer/repository_index_initializer_factory.ts deleted file mode 100644 index 5797b06b1f79c..0000000000000 --- a/x-pack/legacy/plugins/code/server/indexer/repository_index_initializer_factory.ts +++ /dev/null @@ -1,18 +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 { Indexer, IndexerFactory, RepositoryIndexInitializer } from '.'; -import { RepositoryUri } from '../../model'; -import { EsClient } from '../lib/esqueue'; -import { Logger } from '../log'; - -export class RepositoryIndexInitializerFactory implements IndexerFactory { - constructor(protected readonly client: EsClient, protected readonly log: Logger) {} - - public async create(repoUri: RepositoryUri, revision: string): Promise { - return new RepositoryIndexInitializer(repoUri, revision, this.client, this.log); - } -} diff --git a/x-pack/legacy/plugins/code/server/indexer/schema/commit.ts b/x-pack/legacy/plugins/code/server/indexer/schema/commit.ts deleted file mode 100644 index 1bc248787dbf7..0000000000000 --- a/x-pack/legacy/plugins/code/server/indexer/schema/commit.ts +++ /dev/null @@ -1,57 +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 { RepositoryUtils } from '../../../common/repository_utils'; -import { RepositoryUri } from '../../../model'; - -export const CommitSchema = { - repoUri: { - type: 'keyword', - }, - id: { - type: 'keyword', - }, - message: { - type: 'text', - }, - body: { - type: 'text', - }, - date: { - type: 'date', - }, - parents: { - type: 'keyword', - }, - author: { - properties: { - name: { - type: 'text', - }, - email: { - type: 'text', - }, - }, - }, - committer: { - properties: { - name: { - type: 'text', - }, - email: { - type: 'text', - }, - }, - }, -}; - -export const CommitIndexNamePrefix = `.code-commit`; -export const CommitIndexName = (repoUri: RepositoryUri) => { - return `${CommitIndexNamePrefix}-${RepositoryUtils.normalizeRepoUriToIndexName(repoUri)}`; -}; -export const CommitSearchIndexWithScope = (repoScope: RepositoryUri[]) => { - return repoScope.map((repoUri: RepositoryUri) => `${CommitIndexName(repoUri)}*`).join(','); -}; diff --git a/x-pack/legacy/plugins/code/server/indexer/schema/document.ts b/x-pack/legacy/plugins/code/server/indexer/schema/document.ts deleted file mode 100644 index c2a2cd1e334d5..0000000000000 --- a/x-pack/legacy/plugins/code/server/indexer/schema/document.ts +++ /dev/null @@ -1,296 +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 { RepositoryUtils } from '../../../common/repository_utils'; -import { RepositoryUri } from '../../../model'; - -// The field name of repository object nested in the Document index. -export const RepositoryReservedField = 'repository'; -// The field name of repository git status object nested in the Document index. -export const RepositoryGitStatusReservedField = 'repository_git_status'; -// The field name of repository delete status object nested in the Document index. -export const RepositoryDeleteStatusReservedField = 'repository_delete_status'; -// The field name of repository lsp index status object nested in the Document index. -export const RepositoryIndexStatusReservedField = 'repository_index_status'; -// The field name of repository config object nested in the Document index. -export const RepositoryConfigReservedField = 'repository_config'; - -export const ALL_RESERVED = [ - RepositoryReservedField, - RepositoryGitStatusReservedField, - RepositoryDeleteStatusReservedField, - RepositoryIndexStatusReservedField, - RepositoryConfigReservedField, -]; - -// Correspond to model/search/Document -export const DocumentSchema = { - repoUri: { - type: 'keyword', - }, - path: { - type: 'text', - analyzer: 'path_analyzer', - fields: { - hierarchy: { - type: 'text', - analyzer: 'path_hierarchy_analyzer', - }, - keyword: { - type: 'keyword', - }, - }, - }, - content: { - type: 'text', - analyzer: 'content_analyzer', - }, - qnames: { - type: 'text', - analyzer: 'qname_path_hierarchy_analyzer', - }, - language: { - type: 'keyword', - }, - sha1: { - type: 'text', - index: false, - norms: false, - }, - // Repository object resides in this document index. - // There is always a single Repository object in this index. - [RepositoryReservedField]: { - properties: { - uri: { - type: 'text', - }, - url: { - type: 'text', - index: false, - }, - name: { - type: 'text', - }, - org: { - type: 'text', - }, - defaultBranch: { - type: 'keyword', - }, - revision: { - type: 'keyword', - }, - indexedRevision: { - type: 'keyword', - }, - }, - }, - [RepositoryConfigReservedField]: { - properties: { - uri: { - type: 'text', - }, - disableGo: { - type: 'boolean', - }, - disableJava: { - type: 'boolean', - }, - disableTypescript: { - type: 'boolean', - }, - }, - }, - // A single Repository Git Status object resides in this document index. - [RepositoryGitStatusReservedField]: { - properties: { - uri: { - type: 'text', - }, - progress: { - type: 'integer', - }, - timestamp: { - type: 'date', - }, - revision: { - type: 'keyword', - }, - errorMessage: { - type: 'text', - }, - cloneProgress: { - properties: { - isCloned: { - type: 'boolean', - }, - receivedObjects: { - type: 'integer', - }, - indexedObjects: { - type: 'integer', - }, - totalObjects: { - type: 'integer', - }, - localObjects: { - type: 'integer', - }, - totalDeltas: { - type: 'integer', - }, - indexedDeltas: { - type: 'integer', - }, - receivedBytes: { - type: 'integer', - }, - }, - }, - }, - }, - // A single Repository Delete Status object resides in this document index. - [RepositoryDeleteStatusReservedField]: { - properties: { - uri: { - type: 'text', - }, - progress: { - type: 'integer', - }, - timestamp: { - type: 'date', - }, - revision: { - type: 'keyword', - }, - }, - }, - // A single Repository LSP Index Status object resides in this document index. - [RepositoryIndexStatusReservedField]: { - properties: { - uri: { - type: 'text', - }, - progress: { - type: 'integer', - }, - timestamp: { - type: 'date', - }, - revision: { - type: 'keyword', - }, - indexProgress: { - properties: { - type: { - type: 'keyword', - }, - total: { - type: 'integer', - }, - success: { - type: 'integer', - }, - fail: { - type: 'integer', - }, - percentage: { - type: 'integer', - }, - checkpoint: { - type: 'object', - }, - }, - }, - commitIndexProgress: { - properties: { - type: { - type: 'keyword', - }, - total: { - type: 'integer', - }, - success: { - type: 'integer', - }, - fail: { - type: 'integer', - }, - percentage: { - type: 'integer', - }, - checkpoint: { - type: 'object', - }, - }, - }, - }, - }, -}; - -export const DocumentAnalysisSettings = { - analysis: { - analyzer: { - content_analyzer: { - tokenizer: 'standard', - char_filter: ['content_char_filter'], - filter: ['lowercase'], - }, - lowercase_analyzer: { - type: 'custom', - filter: ['lowercase'], - tokenizer: 'keyword', - }, - path_analyzer: { - type: 'custom', - filter: ['lowercase'], - tokenizer: 'path_tokenizer', - }, - path_hierarchy_analyzer: { - type: 'custom', - tokenizer: 'path_hierarchy_tokenizer', - filter: ['lowercase'], - }, - qname_path_hierarchy_analyzer: { - type: 'custom', - tokenizer: 'qname_path_hierarchy_tokenizer', - filter: ['lowercase'], - }, - }, - char_filter: { - content_char_filter: { - type: 'pattern_replace', - pattern: '[.]', - replacement: ' ', - }, - }, - tokenizer: { - path_tokenizer: { - type: 'pattern', - pattern: '[\\\\./]', - }, - qname_path_hierarchy_tokenizer: { - type: 'path_hierarchy', - delimiter: '.', - reverse: 'true', - }, - path_hierarchy_tokenizer: { - type: 'path_hierarchy', - delimiter: '/', - reverse: 'true', - }, - }, - }, -}; - -export const DocumentIndexNamePrefix = `.code-document`; -export const DocumentIndexName = (repoUri: RepositoryUri) => { - return `${DocumentIndexNamePrefix}-${RepositoryUtils.normalizeRepoUriToIndexName(repoUri)}`; -}; -export const DocumentSearchIndexWithScope = (repoScope: RepositoryUri[]) => { - return repoScope.map((repoUri: RepositoryUri) => `${DocumentIndexName(repoUri)}*`).join(','); -}; diff --git a/x-pack/legacy/plugins/code/server/indexer/schema/index.ts b/x-pack/legacy/plugins/code/server/indexer/schema/index.ts deleted file mode 100644 index 4a6f010a0b6d3..0000000000000 --- a/x-pack/legacy/plugins/code/server/indexer/schema/index.ts +++ /dev/null @@ -1,11 +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 * from './commit'; -export * from './document'; -export * from './reference'; -export * from './repository'; -export * from './symbol'; diff --git a/x-pack/legacy/plugins/code/server/indexer/schema/reference.ts b/x-pack/legacy/plugins/code/server/indexer/schema/reference.ts deleted file mode 100644 index 6006e3849c7d0..0000000000000 --- a/x-pack/legacy/plugins/code/server/indexer/schema/reference.ts +++ /dev/null @@ -1,43 +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 { RepositoryUtils } from '../../../common/repository_utils'; -import { RepositoryUri } from '../../../model'; - -export const ReferenceSchema = { - category: { - type: 'keyword', - }, - location: { - properties: { - uri: { - type: 'text', - }, - }, - }, - symbol: { - properties: { - name: { - type: 'text', - }, - kind: { - type: 'keyword', - }, - location: { - properties: { - uri: { - type: 'text', - }, - }, - }, - }, - }, -}; - -export const ReferenceIndexNamePrefix = `.code-reference`; -export const ReferenceIndexName = (repoUri: RepositoryUri) => { - return `${ReferenceIndexNamePrefix}-${RepositoryUtils.normalizeRepoUriToIndexName(repoUri)}`; -}; diff --git a/x-pack/legacy/plugins/code/server/indexer/schema/repository.ts b/x-pack/legacy/plugins/code/server/indexer/schema/repository.ts deleted file mode 100644 index 3024c0ae2693d..0000000000000 --- a/x-pack/legacy/plugins/code/server/indexer/schema/repository.ts +++ /dev/null @@ -1,19 +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 { - DocumentAnalysisSettings, - DocumentIndexName, - DocumentIndexNamePrefix, - DocumentSchema, - DocumentSearchIndexWithScope, -} from './document'; - -export const RepositorySchema = DocumentSchema; -export const RepositoryAnalysisSettings = DocumentAnalysisSettings; -export const RepositoryIndexNamePrefix = DocumentIndexNamePrefix; -export const RepositoryIndexName = DocumentIndexName; -export const RepositorySearchIndexWithScope = DocumentSearchIndexWithScope; diff --git a/x-pack/legacy/plugins/code/server/indexer/schema/symbol.ts b/x-pack/legacy/plugins/code/server/indexer/schema/symbol.ts deleted file mode 100644 index d318730f466a6..0000000000000 --- a/x-pack/legacy/plugins/code/server/indexer/schema/symbol.ts +++ /dev/null @@ -1,80 +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 { RepositoryUtils } from '../../../common/repository_utils'; -import { RepositoryUri } from '../../../model'; - -export const SymbolSchema = { - qname: { - type: 'text', - analyzer: 'qname_path_hierarchy_case_sensitive_analyzer', - fields: { - // Create a 'lowercased' field to match query in lowercased mode. - lowercased: { - type: 'text', - analyzer: 'qname_path_hierarchy_case_insensitive_analyzer', - }, - }, - }, - symbolInformation: { - properties: { - name: { - type: 'text', - analyzer: 'qname_path_hierarchy_case_sensitive_analyzer', - fields: { - // Create a 'lowercased' field to match query in lowercased mode. - lowercased: { - type: 'text', - analyzer: 'qname_path_hierarchy_case_insensitive_analyzer', - }, - }, - }, - kind: { - type: 'integer', - index: false, - }, - location: { - properties: { - uri: { - // Indexed now for symbols batch deleting in incremental indexing - type: 'keyword', - }, - }, - }, - }, - }, -}; - -export const SymbolAnalysisSettings = { - analysis: { - analyzer: { - qname_path_hierarchy_case_sensitive_analyzer: { - type: 'custom', - tokenizer: 'qname_path_hierarchy_tokenizer', - }, - qname_path_hierarchy_case_insensitive_analyzer: { - type: 'custom', - tokenizer: 'qname_path_hierarchy_tokenizer', - filter: ['lowercase'], - }, - }, - tokenizer: { - qname_path_hierarchy_tokenizer: { - type: 'path_hierarchy', - delimiter: '.', - reverse: 'true', - }, - }, - }, -}; - -export const SymbolIndexNamePrefix = `.code-symbol`; -export const SymbolIndexName = (repoUri: RepositoryUri) => { - return `${SymbolIndexNamePrefix}-${RepositoryUtils.normalizeRepoUriToIndexName(repoUri)}`; -}; -export const SymbolSearchIndexWithScope = (repoScope: RepositoryUri[]) => { - return repoScope.map((repoUri: RepositoryUri) => `${SymbolIndexName(repoUri)}*`).join(','); -}; diff --git a/x-pack/legacy/plugins/code/server/indexer/schema/version.json b/x-pack/legacy/plugins/code/server/indexer/schema/version.json deleted file mode 100644 index 00198abef978d..0000000000000 --- a/x-pack/legacy/plugins/code/server/indexer/schema/version.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "codeIndexVersion": "1" -} \ No newline at end of file diff --git a/x-pack/legacy/plugins/code/server/init_es.ts b/x-pack/legacy/plugins/code/server/init_es.ts deleted file mode 100644 index 0b12cddb73983..0000000000000 --- a/x-pack/legacy/plugins/code/server/init_es.ts +++ /dev/null @@ -1,23 +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 { IClusterClient } from 'src/core/server'; -import { RepositoryIndexInitializerFactory } from './indexer'; -import { RepositoryConfigController } from './repository_config_controller'; -import { EsClientWithInternalRequest } from './utils/esclient_with_internal_request'; -import { EsClient } from './lib/esqueue'; -import { Logger } from './log'; - -export async function initEs(cluster: IClusterClient, log: Logger) { - const esClient: EsClient = new EsClientWithInternalRequest(cluster); - const repoConfigController = new RepositoryConfigController(esClient); - const repoIndexInitializerFactory = new RepositoryIndexInitializerFactory(esClient, log); - return { - esClient, - repoConfigController, - repoIndexInitializerFactory, - }; -} diff --git a/x-pack/legacy/plugins/code/server/init_local.ts b/x-pack/legacy/plugins/code/server/init_local.ts deleted file mode 100644 index 4e231316411b9..0000000000000 --- a/x-pack/legacy/plugins/code/server/init_local.ts +++ /dev/null @@ -1,74 +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 { Server } from 'hapi'; - -import { LoggerFactory } from 'src/core/server'; -import { ServerOptions } from './server_options'; -import { CodeServices } from './distributed/code_services'; -import { EsClient } from './lib/esqueue'; -import { RepositoryConfigController } from './repository_config_controller'; -import { GitOperations } from './git_operations'; -import { - getGitServiceHandler, - getLspServiceHandler, - getWorkspaceHandler, - GitServiceDefinition, - GitServiceDefinitionOption, - LspServiceDefinition, - LspServiceDefinitionOption, - SetupDefinition, - setupServiceHandler, - WorkspaceDefinition, -} from './distributed/apis'; -import { InstallManager } from './lsp/install_manager'; -import { LspService } from './lsp/lsp_service'; -import { ServerLoggerFactory } from './utils/server_logger_factory'; - -export function initLocalService( - server: Server, - loggerFactory: LoggerFactory, - serverOptions: ServerOptions, - codeServices: CodeServices, - esClient: EsClient, - repoConfigController: RepositoryConfigController -) { - // Initialize git operations - const gitOps = new GitOperations(serverOptions.repoPath); - codeServices.registerHandler( - GitServiceDefinition, - getGitServiceHandler(gitOps), - GitServiceDefinitionOption - ); - - const serverLoggerFactory = new ServerLoggerFactory(loggerFactory, serverOptions.verbose); - const installManager = new InstallManager(server, serverOptions); - const lspService = new LspService( - '127.0.0.1', - serverOptions, - gitOps, - esClient, - installManager, - serverLoggerFactory, - repoConfigController - ); - server.events.on('stop', async () => { - loggerFactory.get().debug('shutdown lsp process'); - await lspService.shutdown(); - }); - codeServices.registerHandler( - LspServiceDefinition, - getLspServiceHandler(lspService), - LspServiceDefinitionOption - ); - codeServices.registerHandler( - WorkspaceDefinition, - getWorkspaceHandler(serverLoggerFactory, lspService.workspaceHandler) - ); - codeServices.registerHandler(SetupDefinition, setupServiceHandler); - - return { gitOps, lspService, installManager }; -} diff --git a/x-pack/legacy/plugins/code/server/init_queue.ts b/x-pack/legacy/plugins/code/server/init_queue.ts deleted file mode 100644 index 12e8eeba15760..0000000000000 --- a/x-pack/legacy/plugins/code/server/init_queue.ts +++ /dev/null @@ -1,19 +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 { EsClient, Esqueue } from './lib/esqueue'; -import { Logger } from './log'; -import { ServerOptions } from './server_options'; - -export function initQueue(serverOptions: ServerOptions, log: Logger, esClient: EsClient) { - const queueIndex: string = serverOptions.queueIndex; - const queueTimeoutMs: number = serverOptions.queueTimeoutMs; - const queue = new Esqueue(queueIndex, { - client: esClient, - timeout: queueTimeoutMs, - }); - return queue; -} diff --git a/x-pack/legacy/plugins/code/server/init_workers.ts b/x-pack/legacy/plugins/code/server/init_workers.ts deleted file mode 100644 index f20adf375f9a3..0000000000000 --- a/x-pack/legacy/plugins/code/server/init_workers.ts +++ /dev/null @@ -1,105 +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 checkDiskSpace from 'check-disk-space'; - -import { IndexerType } from '../model'; -import { DiskWatermarkService } from './disk_watermark'; -import { EsClient, Esqueue } from './lib/esqueue'; -import { LspService } from './lsp/lsp_service'; -import { GitOperations } from './git_operations'; -import { ServerOptions } from './server_options'; -import { CodeServices } from './distributed/code_services'; -import { CommitIndexerFactory, IndexerFactory, LspIndexerFactory } from './indexer'; -import { CancellationSerivce, CloneWorker, DeleteWorker, IndexWorker, UpdateWorker } from './queue'; -import { RepositoryServiceFactory } from './repository_service_factory'; -import { getRepositoryHandler, RepositoryServiceDefinition } from './distributed/apis'; -import { CloneScheduler, IndexScheduler, UpdateScheduler } from './scheduler'; -import { Logger } from './log'; - -export function initWorkers( - log: Logger, - esClient: EsClient, - queue: Esqueue, - lspService: LspService, - gitOps: GitOperations, - serverOptions: ServerOptions, - codeServices: CodeServices -) { - // Initialize indexing factories. - const lspIndexerFactory = new LspIndexerFactory(lspService, serverOptions, gitOps, esClient, log); - const indexerFactoryMap: Map = new Map(); - indexerFactoryMap.set(IndexerType.LSP, lspIndexerFactory); - - if (serverOptions.enableCommitIndexing) { - const commitIndexerFactory = new CommitIndexerFactory(gitOps, esClient, log); - indexerFactoryMap.set(IndexerType.COMMIT, commitIndexerFactory); - } - - // Initialize queue worker cancellation service. - const cancellationService = new CancellationSerivce(); - const indexWorker = new IndexWorker( - queue, - log, - esClient, - indexerFactoryMap, - gitOps, - cancellationService - ).bind(codeServices); - - const repoServiceFactory: RepositoryServiceFactory = new RepositoryServiceFactory(); - - const watermarkService = new DiskWatermarkService(checkDiskSpace, serverOptions, log); - const cloneWorker = new CloneWorker( - queue, - log, - esClient, - serverOptions, - gitOps, - indexWorker, - repoServiceFactory, - cancellationService, - watermarkService - ).bind(codeServices); - const deleteWorker = new DeleteWorker( - queue, - log, - esClient, - serverOptions, - gitOps, - cancellationService, - lspService, - repoServiceFactory - ).bind(codeServices); - const updateWorker = new UpdateWorker( - queue, - log, - esClient, - serverOptions, - gitOps, - repoServiceFactory, - cancellationService, - watermarkService - ).bind(codeServices); - codeServices.registerHandler( - RepositoryServiceDefinition, - getRepositoryHandler(cloneWorker, deleteWorker, indexWorker) - ); - - // Initialize schedulers. - const updateScheduler = new UpdateScheduler(updateWorker, serverOptions, esClient, log); - const indexScheduler = new IndexScheduler(indexWorker, serverOptions, esClient, log); - updateScheduler.start(); - indexScheduler.start(); - // Check if the repository is local on the file system. - // This should be executed once at the startup time of Kibana. - // Ignored in cluster mode, leave it to the node level control loop - if (!serverOptions.clusterEnabled) { - const cloneScheduler = new CloneScheduler(cloneWorker, serverOptions, esClient, log); - cloneScheduler.schedule(); - } - return { indexScheduler, updateScheduler, cloneWorker, deleteWorker, indexWorker, updateWorker }; -} diff --git a/x-pack/legacy/plugins/code/server/lib/esqueue/constants/default_settings.js b/x-pack/legacy/plugins/code/server/lib/esqueue/constants/default_settings.js deleted file mode 100644 index 5df580a063dd9..0000000000000 --- a/x-pack/legacy/plugins/code/server/lib/esqueue/constants/default_settings.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 defaultSettings = { - DEFAULT_SETTING_TIMEOUT: 10000, - DEFAULT_SETTING_DATE_SEPARATOR: '-', - DEFAULT_SETTING_INTERVAL: 'week', - DEFAULT_SETTING_INDEX_SETTINGS: { - number_of_shards: 1, - auto_expand_replicas: '0-1', - }, -}; diff --git a/x-pack/legacy/plugins/code/server/lib/esqueue/constants/events.d.ts b/x-pack/legacy/plugins/code/server/lib/esqueue/constants/events.d.ts deleted file mode 100644 index e160951d54183..0000000000000 --- a/x-pack/legacy/plugins/code/server/lib/esqueue/constants/events.d.ts +++ /dev/null @@ -1,25 +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. - */ - -declare class Events { - public EVENT_QUEUE_ERROR: 'queue:error'; - public EVENT_JOB_ERROR: 'job:error'; - public EVENT_JOB_CREATED: 'job:created'; - public EVENT_JOB_CREATE_ERROR: 'job:creation error'; - public EVENT_WORKER_COMPLETE: 'worker:job complete'; - public EVENT_WORKER_RESET_PROCESSING_JOB_ERROR: 'worker:reset job processing error'; - public EVENT_WORKER_JOB_CLAIM_ERROR: 'worker:claim job error'; - public EVENT_WORKER_JOB_SEARCH_ERROR: 'worker:pending jobs error'; - public EVENT_WORKER_JOB_UPDATE_ERROR: 'worker:update job error'; - public EVENT_WORKER_JOB_FAIL: 'worker:job failed'; - public EVENT_WORKER_JOB_FAIL_ERROR: 'worker:failed job update error'; - public EVENT_WORKER_JOB_EXECUTION_ERROR: 'worker:job execution error'; - public EVENT_WORKER_JOB_TIMEOUT: 'worker:job timeout'; -} - -declare const events: Events; - -export { events }; diff --git a/x-pack/legacy/plugins/code/server/lib/esqueue/constants/events.js b/x-pack/legacy/plugins/code/server/lib/esqueue/constants/events.js deleted file mode 100644 index 126f0eb51f2c9..0000000000000 --- a/x-pack/legacy/plugins/code/server/lib/esqueue/constants/events.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. - */ - -export const events = { - EVENT_QUEUE_ERROR: 'queue:error', - EVENT_JOB_ERROR: 'job:error', - EVENT_JOB_CREATED: 'job:created', - EVENT_JOB_CREATE_ERROR: 'job:creation error', - EVENT_WORKER_COMPLETE: 'worker:job complete', - EVENT_WORKER_RESET_PROCESSING_JOB_ERROR: 'worker:reset job processing error', - EVENT_WORKER_JOB_CLAIM_ERROR: 'worker:claim job error', - EVENT_WORKER_JOB_SEARCH_ERROR: 'worker:pending jobs error', - EVENT_WORKER_JOB_UPDATE_ERROR: 'worker:update job error', - EVENT_WORKER_JOB_FAIL: 'worker:job failed', - EVENT_WORKER_JOB_FAIL_ERROR: 'worker:failed job update error', - EVENT_WORKER_JOB_EXECUTION_ERROR: 'worker:job execution error', - EVENT_WORKER_JOB_TIMEOUT: 'worker:job timeout', -}; diff --git a/x-pack/legacy/plugins/code/server/lib/esqueue/constants/index.js b/x-pack/legacy/plugins/code/server/lib/esqueue/constants/index.js deleted file mode 100644 index 5f9efddc82dfd..0000000000000 --- a/x-pack/legacy/plugins/code/server/lib/esqueue/constants/index.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. - */ - -import { events } from './events'; -import { statuses } from './statuses'; -import { defaultSettings } from './default_settings'; - -export const constants = { - ...events, - ...statuses, - ...defaultSettings -}; diff --git a/x-pack/legacy/plugins/code/server/lib/esqueue/constants/statuses.d.ts b/x-pack/legacy/plugins/code/server/lib/esqueue/constants/statuses.d.ts deleted file mode 100644 index 43b96237de27e..0000000000000 --- a/x-pack/legacy/plugins/code/server/lib/esqueue/constants/statuses.d.ts +++ /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 type Status = 'pending' | 'processing' | 'completed' | 'failed' | 'cancelled'; diff --git a/x-pack/legacy/plugins/code/server/lib/esqueue/constants/statuses.js b/x-pack/legacy/plugins/code/server/lib/esqueue/constants/statuses.js deleted file mode 100644 index 620a567e18fe7..0000000000000 --- a/x-pack/legacy/plugins/code/server/lib/esqueue/constants/statuses.js +++ /dev/null @@ -1,13 +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 statuses = { - JOB_STATUS_PENDING: 'pending', - JOB_STATUS_PROCESSING: 'processing', - JOB_STATUS_COMPLETED: 'completed', - JOB_STATUS_FAILED: 'failed', - JOB_STATUS_CANCELLED: 'cancelled', -}; diff --git a/x-pack/legacy/plugins/code/server/lib/esqueue/esqueue.d.ts b/x-pack/legacy/plugins/code/server/lib/esqueue/esqueue.d.ts deleted file mode 100644 index 14f7c3da46c68..0000000000000 --- a/x-pack/legacy/plugins/code/server/lib/esqueue/esqueue.d.ts +++ /dev/null @@ -1,64 +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 { EventEmitter } from 'events'; - -import { events } from './constants/events'; -import { Job, JobOptions } from './job'; -import { AnyObject, EsClient, LogFn } from './misc'; -import { Worker, WorkerFn, WorkerOptions, WorkerOutput } from './worker'; - -export class Esqueue extends EventEmitter { - constructor( - /** - * The base name Esqueue will use for its time-based job indices in Elasticsearch. This - * will have a date string appended to it to determine the actual index name. - */ - index: string, - options: { - /** - * The Elasticsearch client EsQueue will use to query ES and manage its queue indices - */ - client: EsClient; - - /** - * A function that Esqueue will call with log messages - */ - logger?: LogFn; - - /** - * Interval that Esqueue will use when creating its time-based job indices in Elasticsearch - */ - interval?: 'year' | 'month' | 'week' | 'day' | 'hour' | 'minute'; - - /** - * Default job timeout - */ - timeout?: number; - - /** - * The value used to separate the parts of the date in index names created by Esqueue - */ - dateSeparator?: string; - - /** - * Arbitrary settings that will be merged with the default index settings EsQueue uses to - * create elastcisearch indices - */ - indexSettings?: AnyObject; - } - ); - - public addJob>(type: string, payload: P, options: JobOptions): J; - - public registerWorker>( - this: void, - type: string, - workerFn: WorkerFn, - opts?: Pick> - ): W; - public destroy(): void; -} diff --git a/x-pack/legacy/plugins/code/server/lib/esqueue/esqueue.js b/x-pack/legacy/plugins/code/server/lib/esqueue/esqueue.js deleted file mode 100644 index 360a2aebf6305..0000000000000 --- a/x-pack/legacy/plugins/code/server/lib/esqueue/esqueue.js +++ /dev/null @@ -1,82 +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. - */ - -/* - * Borrowed from https://github.com/elastic/kibana/tree/master/x-pack/legacy/plugins/reporting/server/lib/esqueue - * TODO(mengwei): need to abstract this esqueue as a common library when merging into kibana's main repo. - */ - -import { EventEmitter } from 'events'; -import { Job } from './job'; -import { Worker } from './worker'; -import { constants } from './constants'; -import { indexTimestamp } from './helpers/index_timestamp'; - -function omit(obj, keysToOmit) { - return Object.keys(obj).reduce((acc, key) => ( - keysToOmit.includes(key) ? acc : { ...acc, [key]: obj[key] } - ), {}); -} - -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.ping(), - ]; - - return Promise.all(initTasks).catch((err) => { - this._logger(err, ['initTasks', 'error']); - throw err; - }); - } - - addJob(type, 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, type, 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/legacy/plugins/code/server/lib/esqueue/helpers/cancellation_token.d.ts b/x-pack/legacy/plugins/code/server/lib/esqueue/helpers/cancellation_token.d.ts deleted file mode 100644 index b0452b61022dc..0000000000000 --- a/x-pack/legacy/plugins/code/server/lib/esqueue/helpers/cancellation_token.d.ts +++ /dev/null @@ -1,10 +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 class CancellationToken { - public on(callback: (reason: string) => void): void; - public cancel(reason: string): void; -} diff --git a/x-pack/legacy/plugins/code/server/lib/esqueue/helpers/cancellation_token.js b/x-pack/legacy/plugins/code/server/lib/esqueue/helpers/cancellation_token.js deleted file mode 100644 index 0996d9725132f..0000000000000 --- a/x-pack/legacy/plugins/code/server/lib/esqueue/helpers/cancellation_token.js +++ /dev/null @@ -1,29 +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 class CancellationToken { - constructor() { - this.isCancelled = false; - this._callbacks = []; - } - - on = (callback) => { - if (typeof callback !== 'function') { - throw new Error('Expected callback to be a function'); - } - - if (this.isCancelled) { - return; - } - - this._callbacks.push(callback); - }; - - cancel = (reason) => { - this.isCancelled = true; - this._callbacks.forEach(callback => callback(reason)); - }; -} diff --git a/x-pack/legacy/plugins/code/server/lib/esqueue/helpers/create_index.js b/x-pack/legacy/plugins/code/server/lib/esqueue/helpers/create_index.js deleted file mode 100644 index 48af625311bea..0000000000000 --- a/x-pack/legacy/plugins/code/server/lib/esqueue/helpers/create_index.js +++ /dev/null @@ -1,90 +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 { 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 phone home 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 - } - } - }, - } - }, - 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' }, - status: { type: 'keyword' }, - output: { - type: 'object', - properties: { - content_type: { type: 'keyword' }, - 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.indices.exists({ - index: indexName, - }) - .then((exists) => { - if (!exists) { - return client.indices.create({ - index: indexName, - body: body - }) - .then(() => true); - } - return exists; - }); -} diff --git a/x-pack/legacy/plugins/code/server/lib/esqueue/helpers/errors.js b/x-pack/legacy/plugins/code/server/lib/esqueue/helpers/errors.js deleted file mode 100644 index b8be62d5ad455..0000000000000 --- a/x-pack/legacy/plugins/code/server/lib/esqueue/helpers/errors.js +++ /dev/null @@ -1,26 +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 function WorkerTimeoutError(message, props = {}) { - this.name = 'WorkerTimeoutError'; - this.message = message; - this.timeout = props.timeout; - this.jobId = props.jobId; - - if ('captureStackTrace' in Error) Error.captureStackTrace(this, WorkerTimeoutError); - else this.stack = (new Error()).stack; -} -WorkerTimeoutError.prototype = Object.create(Error.prototype); - -export function UnspecifiedWorkerError(message, props = {}) { - this.name = 'UnspecifiedWorkerError'; - this.message = message; - this.jobId = props.jobId; - - if ('captureStackTrace' in Error) Error.captureStackTrace(this, UnspecifiedWorkerError); - else this.stack = (new Error()).stack; -} -UnspecifiedWorkerError.prototype = Object.create(Error.prototype); diff --git a/x-pack/legacy/plugins/code/server/lib/esqueue/helpers/index_timestamp.js b/x-pack/legacy/plugins/code/server/lib/esqueue/helpers/index_timestamp.js deleted file mode 100644 index c03be00030650..0000000000000 --- a/x-pack/legacy/plugins/code/server/lib/esqueue/helpers/index_timestamp.js +++ /dev/null @@ -1,46 +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 moment from 'moment'; - -export const intervals = [ - 'year', - 'month', - 'week', - 'day', - 'hour', - 'minute' -]; - -export function indexTimestamp(intervalStr, separator = '-') { - if (separator.match(/[a-z]/i)) throw new Error('Interval separator can not be a letter'); - - const index = intervals.indexOf(intervalStr); - if (index === -1) throw new Error('Invalid index interval: ', intervalStr); - - const m = moment(); - m.startOf(intervalStr); - - let dateString; - switch (intervalStr) { - case 'year': - dateString = 'YYYY'; - break; - case 'month': - dateString = `YYYY${separator}MM`; - break; - case 'hour': - dateString = `YYYY${separator}MM${separator}DD${separator}HH`; - break; - case 'minute': - dateString = `YYYY${separator}MM${separator}DD${separator}HH${separator}mm`; - break; - default: - dateString = `YYYY${separator}MM${separator}DD`; - } - - return m.format(dateString); -} diff --git a/x-pack/legacy/plugins/code/server/lib/esqueue/helpers/poller.js b/x-pack/legacy/plugins/code/server/lib/esqueue/helpers/poller.js deleted file mode 100644 index fd6a878b4e7d6..0000000000000 --- a/x-pack/legacy/plugins/code/server/lib/esqueue/helpers/poller.js +++ /dev/null @@ -1,81 +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. - */ - -/* - * Borrowed from https://github.com/elastic/kibana/blob/master/x-pack/legacy/common/poller.js - */ - -import _ from 'lodash'; - -export class Poller { - - constructor(options) { - this.functionToPoll = options.functionToPoll; // Must return a Promise - this.successFunction = options.successFunction || _.noop; - this.errorFunction = options.errorFunction || _.noop; - this.pollFrequencyInMillis = options.pollFrequencyInMillis; - this.trailing = options.trailing || false; - this.continuePollingOnError = options.continuePollingOnError || false; - this.pollFrequencyErrorMultiplier = options.pollFrequencyErrorMultiplier || 1; - this._timeoutId = null; - this._isRunning = false; - } - - getPollFrequency() { - return this.pollFrequencyInMillis; - } - - _poll() { - return this.functionToPoll() - .then(this.successFunction) - .then(() => { - if (!this._isRunning) { - return; - } - - this._timeoutId = setTimeout(this._poll.bind(this), this.pollFrequencyInMillis); - }) - .catch(e => { - this.errorFunction(e); - if (!this._isRunning) { - return; - } - - if (this.continuePollingOnError) { - this._timeoutId = setTimeout(this._poll.bind(this), this.pollFrequencyInMillis * this.pollFrequencyErrorMultiplier); - } else { - this.stop(); - } - }); - } - - start() { - if (this._isRunning) { - return; - } - - this._isRunning = true; - if (this.trailing) { - this._timeoutId = setTimeout(this._poll.bind(this), this.pollFrequencyInMillis); - } else { - this._poll(); - } - } - - stop() { - if (!this._isRunning) { - return; - } - - this._isRunning = false; - clearTimeout(this._timeoutId); - this._timeoutId = null; - } - - isRunning() { - return this._isRunning; - } -} diff --git a/x-pack/legacy/plugins/code/server/lib/esqueue/index.d.ts b/x-pack/legacy/plugins/code/server/lib/esqueue/index.d.ts deleted file mode 100644 index d276e06b3a139..0000000000000 --- a/x-pack/legacy/plugins/code/server/lib/esqueue/index.d.ts +++ /dev/null @@ -1,12 +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 { events } from './constants/events'; -export { CancellationToken } from './helpers/cancellation_token'; -export { Job } from './job'; -export { Worker, WorkerOutput } from './worker'; -export { Esqueue } from './esqueue'; -export { AnyObject, EsClient } from './misc'; diff --git a/x-pack/legacy/plugins/code/server/lib/esqueue/index.js b/x-pack/legacy/plugins/code/server/lib/esqueue/index.js deleted file mode 100644 index 54c7502630715..0000000000000 --- a/x-pack/legacy/plugins/code/server/lib/esqueue/index.js +++ /dev/null @@ -1,9 +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 { CancellationToken } from './helpers/cancellation_token'; -export { events } from './constants/events'; -export { Esqueue } from './esqueue'; diff --git a/x-pack/legacy/plugins/code/server/lib/esqueue/job.d.ts b/x-pack/legacy/plugins/code/server/lib/esqueue/job.d.ts deleted file mode 100644 index 5d8784a9acf57..0000000000000 --- a/x-pack/legacy/plugins/code/server/lib/esqueue/job.d.ts +++ /dev/null @@ -1,95 +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 { EventEmitter } from 'events'; - -import { events } from './constants/events'; -import { Status } from './constants/statuses'; -import { Esqueue } from './esqueue'; -import { CancellationToken } from './helpers/cancellation_token'; -import { AnyObject, EsClient, LogFn } from './misc'; - -export interface JobOptions { - client?: EsClient; - indexSettings?: string; - created_by?: string; - timeout?: number; - max_attempts?: number; - priority?: number; - headers?: { - [key: string]: string; - }; - logger?: LogFn; -} - -type OptionalPropType = P extends keyof T ? T[P] : void; - -export class Job

extends EventEmitter { - public queue: Esqueue; - public client: EsClient; - public id: string; - public index: string; - public jobtype: string; - public payload: P; - public created_by: string | false; // tslint:disable-line variable-name - public timeout: number; - public maxAttempts: number; - public priority: number; - public indexSettings: AnyObject; - public ready: Promise; - - constructor(queue: Esqueue, index: string, type: string, payload: P, options?: JobOptions); - - /** - * Read the job document out of elasticsearch, includes its current - * status and posisble result. - */ - public get(): Promise<{ - // merged in get() method - index: string; - id: string; - type: string; - version: number; - - // from doc._source - jobtype: string; - meta: { - objectType: OptionalPropType; - layout: OptionalPropType; - }; - payload: P; - priority: number; - created_by: string | false; - timeout: number; - process_expiration: string; // use epoch so the job query works - created_at: string; - attempts: number; - max_attempts: number; - status: Status; - }>; - - /** - * Get a plain JavaScript representation of the Job object - */ - public toJSON(): { - id: string; - index: string; - type: string; - jobtype: string; - created_by: string | false; - payload: P; - timeout: number; - max_attempts: number; - priority: number; - }; - - public on( - name: typeof events['EVENT_JOB_CREATED'], - handler: (info: { id: string; type: string; index: string; version: number }) => void - ): this; - public on(name: typeof events['EVENT_JOB_CREATE_ERROR'], handler: (error: Error) => void): this; - public on(name: string, ...args: any[]): this; -} diff --git a/x-pack/legacy/plugins/code/server/lib/esqueue/job.js b/x-pack/legacy/plugins/code/server/lib/esqueue/job.js deleted file mode 100644 index 4c8f5ee73b277..0000000000000 --- a/x-pack/legacy/plugins/code/server/lib/esqueue/job.js +++ /dev/null @@ -1,134 +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 events from 'events'; - -import Puid from 'puid'; - -import { constants } from './constants'; -import { createIndex } from './helpers/create_index'; - -const puid = new Puid(); - -export class Job extends events.EventEmitter { - constructor(queue, index, type, payload, options = {}) { - if (typeof type !== 'string') throw new Error('Type must be a string'); - if (!payload || typeof payload !== 'object') throw new Error('Payload must be a plain object'); - - super(); - - this.queue = queue; - this.client = options.client || this.queue.client; - this.id = puid.generate(); - this.index = index; - this.jobtype = type; - 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.debug = (msg, err) => { - const logger = options.logger || function () {}; - const message = `${this.id} - ${msg}`; - const tags = ['job', '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.type, - 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, - } - }; - - if (options.headers) { - indexParams.headers = options.headers; - } - - this.ready = createIndex(this.client, this.index, this.indexSettings) - .then(() => this.client.index(indexParams)) - .then((doc) => { - this.document = { - id: doc._id, - type: doc._type, - index: doc._index, - version: doc._version, - }; - this.debug(`Job created in index ${this.index}`); - - return this.client.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.get({ - index: this.index, - id: this.id - }); - }) - .then((doc) => { - return Object.assign(doc._source, { - index: doc._index, - id: doc._id, - type: doc._type, - version: doc._version, - }); - }); - } - - 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 - }; - } -} diff --git a/x-pack/legacy/plugins/code/server/lib/esqueue/misc.d.ts b/x-pack/legacy/plugins/code/server/lib/esqueue/misc.d.ts deleted file mode 100644 index f4ade9c72204d..0000000000000 --- a/x-pack/legacy/plugins/code/server/lib/esqueue/misc.d.ts +++ /dev/null @@ -1,39 +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 interface AnyObject { - [key: string]: any; -} - -export interface EsClient { - indices: { - exists(params: AnyObject): Promise; - create(params: AnyObject): Promise; - refresh(params: AnyObject): Promise; - delete(params: AnyObject): Promise; - - existsAlias(params: AnyObject): Promise; - getAlias(params: AnyObject): Promise; - putAlias(params: AnyObject): Promise; - deleteAlias(params: AnyObject): Promise; - updateAliases(params: AnyObject): Promise; - - getMapping(params: AnyObject): Promise; - }; - - ping(): Promise; - bulk(params: AnyObject): Promise; - index(params: AnyObject): Promise; - get(params: AnyObject): Promise; - update(params: AnyObject): Promise; - reindex(params: AnyObject): Promise; - search(params: AnyObject): Promise; - delete(params: AnyObject): Promise; - deleteByQuery(params: AnyObject): Promise; - updateByQuery(params: AnyObject): Promise; -} - -export type LogFn = (msg: string | Error, tags: string[]) => void; diff --git a/x-pack/legacy/plugins/code/server/lib/esqueue/worker.d.ts b/x-pack/legacy/plugins/code/server/lib/esqueue/worker.d.ts deleted file mode 100644 index 7fe6cc630af9f..0000000000000 --- a/x-pack/legacy/plugins/code/server/lib/esqueue/worker.d.ts +++ /dev/null @@ -1,98 +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 { EventEmitter } from 'events'; - -import { events } from './constants/events'; -import { Esqueue } from './esqueue'; -import { CancellationToken } from './helpers/cancellation_token'; -import { Job } from './job'; -import { AnyObject, EsClient, LogFn } from './misc'; - -type Handler = (arg: A) => void; - -interface JobInfo { - index: string; - type: string; - id: string; -} - -interface WorkerInfo { - id: string; - index: string; - jobType: string; -} - -interface ErrorInfo { - error: Error; - worker: WorkerInfo; - job: JobInfo; -} - -interface ErrorInfoNoJob { - error: Error; - worker: WorkerInfo; -} - -type WorkerOutput = { - content: T; - content_type: string; - max_size_reached?: any; -} | void; - -export type WorkerFn = ( - payload: P, - cancellationToken: CancellationToken -) => Promise; - -export interface WorkerOptions { - interval: number; - capacity: number; - intervalErrorMultiplier: number; - client?: EsClient; - size?: number; - logger?: LogFn; -} - -export class Worker extends EventEmitter { - public id: string; - public queue: Esqueue; - public client: EsClient; - public jobType: string; - public workerFn: WorkerFn; - public checkSize: number; - constructor(queue: Esqueue, type: string, workerFn: WorkerFn, opts: WorkerOptions); - - public destroy(): void; - - /** - * Get a plain JavaScript object describing this worker - */ - public toJSON(): { - id: string; - index: string; - jobType: string; - }; - - public on( - name: typeof events['EVENT_WORKER_COMPLETE'], - h: Handler<{ - job: { - index: string; - type: string; - id: string; - }; - output: O; - }> - ): this; - public on(name: typeof events['EVENT_WORKER_JOB_CLAIM_ERROR'], h: Handler): this; - public on(name: typeof events['EVENT_WORKER_JOB_SEARCH_ERROR'], h: Handler): this; - public on( - name: typeof events['EVENT_WORKER_JOB_FAIL'], - h: Handler<{ job: JobInfo; worker: WorkerInfo; output: O }> - ): this; - public on(name: string, ...args: any[]): this; -} diff --git a/x-pack/legacy/plugins/code/server/lib/esqueue/worker.js b/x-pack/legacy/plugins/code/server/lib/esqueue/worker.js deleted file mode 100644 index 6c8d8442741ba..0000000000000 --- a/x-pack/legacy/plugins/code/server/lib/esqueue/worker.js +++ /dev/null @@ -1,489 +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 events from 'events'; -import Puid from 'puid'; -import moment from 'moment'; -import { constants } from './constants'; -import { WorkerTimeoutError, UnspecifiedWorkerError } from './helpers/errors'; -import { CancellationToken } from './helpers/cancellation_token'; -import { Poller } from './helpers/poller'; - -const puid = new Puid(); - -function formatJobObject(job) { - return { - index: job._index, - id: job._id, - // Expose the payload of the job even when the job failed/timeout - ...job._source.payload, - }; -} - -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.queue = queue; - this.client = opts.client || this.queue.client; - this.codeServices = opts.codeServices; - this.jobtype = type; - this.workerFn = workerFn; - this.checkSize = opts.size || 10; - this.capacity = opts.capacity || 2; - this.processingJobCount = 0; - - this.debug = (msg, err) => { - const logger = opts.logger || function () {}; - - const message = `${this.id} - ${msg}`; - const tags = ['worker', 'debug']; - - if (err) { - logger(`${message}: ${err.stack ? err.stack : err }`, tags); - return; - } - - logger(message, tags); - }; - - this._running = true; - this.debug(`Created worker for job type ${this.jobtype}`); - - this._poller = new Poller({ - functionToPoll: () => { - this._processPendingJobs(); - // Return an empty promise so that the processing jobs won't block the next poll. - return Promise.resolve(); - }, - pollFrequencyInMillis: opts.interval, - trailing: true, - continuePollingOnError: true, - pollFrequencyErrorMultiplier: opts.intervalErrorMultiplier, - }); - // Reset all the existing processing jobs of this particular type. - this._resetProcessingJobs(); - 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, - }; - - return this.client.update({ - index: job._index, - id: job._id, - if_seq_no: job._seq_no, - if_primary_term: job._primary_term, - body: { doc } - }) - .then((response) => { - const updatedJob = { - ...job, - ...response - }; - updatedJob._source = { - ...job._source, - ...doc - }; - return updatedJob; - }) - .catch((err) => { - if (err.statusCode === 409) return true; - this.debug(`_claimJob failed on job ${job._id}`, err); - this.emit(constants.EVENT_WORKER_JOB_CLAIM_ERROR, this._formatErrorParams(err, job)); - return false; - }); - } - - _failJob(job, output = false) { - this.debug(`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.update({ - index: job._index, - id: job._id, - if_seq_no: job._seq_no, - if_primary_term: job._primary_term, - body: { doc } - }) - .then(() => true) - .catch((err) => { - if (err.statusCode === 409) return true; - this.debug(`_failJob failed to update job ${job._id}`, err); - this.emit(constants.EVENT_WORKER_FAIL_UPDATE_ERROR, this._formatErrorParams(err, job)); - return false; - }); - } - - _cancelJob(job) { - this.debug(`Cancelling job ${job._id}`); - - const completedTime = moment().toISOString(); - const doc = { - status: constants.JOB_STATUS_CANCELLED, - completed_at: completedTime, - }; - - return this.client.update({ - index: job._index, - id: job._id, - version: job._version, - body: { doc } - }) - .then(() => true) - .catch((err) => { - if (err.statusCode === 409) return true; - this.debug(`_cancelJob 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; - } else { - docOutput.content = output || defaultOutput; - docOutput.content_type = unknownMime; - } - - return docOutput; - } - - _performJob(job) { - this.debug(`Starting job ${job._id}`); - - const workerOutput = new Promise((resolve, reject) => { - // run the worker's workerFn - let isResolved = false; - const cancellationToken = new CancellationToken(); - cancellationToken.on(() => { - this._cancelJob(job); - }); - this.processingJobCount += 1; - Promise.resolve(this.workerFn.call(null, job._source.payload, cancellationToken)) - .then((res) => { - isResolved = true; - this.processingJobCount -= 1; - resolve(res); - }) - .catch((err) => { - isResolved = true; - this.processingJobCount -= 1; - reject(err); - }); - - // fail if workerFn doesn't finish before timeout - setTimeout(() => { - if (isResolved) return; - - cancellationToken.cancel(); - this.processingJobCount -= 1; - this.debug(`Timeout processing job ${job._id}`); - reject(new WorkerTimeoutError(`Worker timed out, timeout = ${job._source.timeout}`, { - timeout: job._source.timeout, - jobId: job._id, - })); - }, job._source.timeout); - }); - - return workerOutput.then((output) => { - // job execution was successful - this.debug(`Completed job ${job._id}`); - - const completedTime = moment().toISOString(); - const docOutput = this._formatOutput(output); - - const doc = { - status: constants.JOB_STATUS_COMPLETED, - completed_at: completedTime, - output: docOutput - }; - - return this.client.update({ - index: job._index, - id: job._id, - if_seq_no: job._seq_no, - if_primary_term: job._primary_term, - body: { doc } - }) - .then(() => { - const eventOutput = { - job: formatJobObject(job), - output: docOutput, - }; - - this.emit(constants.EVENT_WORKER_COMPLETE, eventOutput); - }) - .catch((err) => { - if (err.statusCode === 409) return false; - this.debug(`Failure saving job output ${job._id}`, err); - this.emit(constants.EVENT_WORKER_JOB_UPDATE_ERROR, this._formatErrorParams(err, job)); - }); - }, (jobErr) => { - if (!jobErr) { - jobErr = new UnspecifiedWorkerError('Unspecified worker error', { - jobId: job._id, - }); - } - - // job execution failed - if (jobErr.name === 'WorkerTimeoutError') { - this.debug(`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.debug(`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 = 0; - - return jobs.reduce((chain, job) => { - return chain.then((claimedJobs) => { - // Apply capacity control to make sure there won't be more jobs processing than the capacity. - if (claimed === (this.capacity - this.processingJobCount)) return claimedJobs; - - return this._claimJob(job) - .then((claimResult) => { - if (claimResult !== false) { - claimed += 1; - claimedJobs.push(claimResult); - return claimedJobs; - } - }); - }); - }, Promise.resolve([])) - .then((claimedJobs) => { - if (!claimedJobs || claimedJobs.length === 0) { - this.debug(`All ${jobs.length} jobs already claimed`); - return; - } - this.debug(`Claimed ${claimedJobs.size} jobs`); - return Promise.all(claimedJobs.map((job) => { - return this._performJob(job); - })); - }) - .catch((err) => { - this.debug('Error claiming jobs', err); - }); - } - - _resetProcessingJobs() { - const nowTime = moment().toISOString(); - const query = { - query: { - bool: { - filter: { - bool: { - minimum_should_match: 1, - must: { term: { jobtype: this.jobtype } }, - should: [ - { - bool: { - must: [ - // Conditioned on the 'processing' jobs which have not - // expired yet. - { term: { status: 'processing' } }, - { range: { process_expiration: { gt: nowTime } } } - ], - }, - }, - ], - } - } - } - }, - script: { - source: `ctx._source.status = "${constants.JOB_STATUS_PENDING}"`, - lang: 'painless', - } - }; - - return this.client.updateByQuery({ - index: `${this.queue.index}-*`, - version: true, - body: query - }) - .then((results) => { - return results.updated; - }) - .catch((err) => { - this.debug('job querying failed', err); - this.emit(constants.EVENT_WORKER_RESET_PROCESSING_JOB_ERROR, this._formatErrorParams(err)); - throw 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, - must: { term: { jobtype: this.jobtype } }, - should: [ - { term: { status: 'pending' } }, - { - bool: { - must: [ - { term: { status: 'processing' } }, - { range: { process_expiration: { lte: nowTime } } } - ], - }, - }, - ], - } - } - } - }, - sort: [ - { priority: { order: 'asc' } }, - { created_at: { order: 'asc' } } - ], - size: this.checkSize - }; - - return this.client.search({ - index: `${this.queue.index}-*`, - version: true, - body: query - }) - .then(async (results) => { - const jobs = results.hits.hits; - if (jobs.length > 0) { - this.debug(`${jobs.length} outstanding jobs returned`); - } - const filtered = []; - for (const job of jobs) { - const payload = job._source.payload.payload; - const repoUrl = payload.uri || payload.url; - const isLocal = await this.codeServices.isResourceLocal(repoUrl); - if (isLocal) { - filtered.push(job); - } - } - return filtered; - }) - .catch((err) => { - // ignore missing indices errors - if (err && err.status === 404) return []; - - this.debug('job querying failed', err); - this.emit(constants.EVENT_WORKER_JOB_SEARCH_ERROR, this._formatErrorParams(err)); - throw err; - }); - } -} diff --git a/x-pack/legacy/plugins/code/server/lib/esqueue/yarn.lock b/x-pack/legacy/plugins/code/server/lib/esqueue/yarn.lock deleted file mode 100644 index 9f8ae5a32aca4..0000000000000 --- a/x-pack/legacy/plugins/code/server/lib/esqueue/yarn.lock +++ /dev/null @@ -1,13 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -moment@^2.20.1: - version "2.22.2" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66" - integrity sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y= - -puid@1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/puid/-/puid-1.0.5.tgz#8d387bf05fb239c5e6f45902c49470084009638c" - integrity sha1-jTh78F+yOcXm9FkCxJRwCEAJY4w= diff --git a/x-pack/legacy/plugins/code/server/log.ts b/x-pack/legacy/plugins/code/server/log.ts deleted file mode 100644 index cb4a5e89aff3d..0000000000000 --- a/x-pack/legacy/plugins/code/server/log.ts +++ /dev/null @@ -1,109 +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 { inspect } from 'util'; -import { Logger as VsLogger } from 'vscode-jsonrpc'; - -import { Logger as KibanaLogger, LoggerFactory } from 'src/core/server'; - -export class Logger implements VsLogger { - private logger: KibanaLogger; - - constructor( - private readonly loggerFactory: LoggerFactory, - private readonly verbose: boolean = false, - private readonly baseTags: string[] = [] - ) { - this.logger = this.loggerFactory.get(...this.baseTags); - } - - // Return a new logger with new tags - public addTags(tags: string[]): Logger { - return new Logger(this.loggerFactory, this.verbose, this.baseTags.concat(tags)); - } - - public info(msg: string | any) { - if (typeof msg !== 'string') { - msg = inspect(msg, { - colors: process.stdout.isTTY, - }); - } - this.logger.info(msg); - } - - public error(msg: string | any) { - if (msg instanceof Error) { - msg = msg.stack; - } - - if (typeof msg !== 'string') { - msg = inspect(msg, { - colors: process.stdout.isTTY, - }); - } - - this.logger.error(msg); - } - - public log(msg: string): void { - this.logger.info(msg); - } - - public debug(msg: string | any) { - if (typeof msg !== 'string') { - msg = inspect(msg, { - colors: process.stdout.isTTY, - }); - } - if (this.verbose) { - this.logger.info(msg); - } else { - this.logger.debug(msg); - } - } - - public warn(msg: string | any): void { - if (msg instanceof Error) { - msg = msg.stack; - } - - if (typeof msg !== 'string') { - msg = inspect(msg, { - colors: process.stdout.isTTY, - }); - } - - this.logger.warn(msg); - } - - // Log subprocess stdout - public stdout(msg: string | any) { - if (typeof msg !== 'string') { - msg = inspect(msg, { - colors: process.stdout.isTTY, - }); - } - if (this.verbose) { - this.logger.info(msg); - } else { - this.logger.debug(msg); - } - } - - // Log subprocess stderr - public stderr(msg: string | any) { - if (typeof msg !== 'string') { - msg = inspect(msg, { - colors: process.stdout.isTTY, - }); - } - if (this.verbose) { - this.logger.error(msg); - } else { - this.logger.debug(msg); - } - } -} diff --git a/x-pack/legacy/plugins/code/server/lsp/abstract_launcher.ts b/x-pack/legacy/plugins/code/server/lsp/abstract_launcher.ts deleted file mode 100644 index 769e3cc2cf45f..0000000000000 --- a/x-pack/legacy/plugins/code/server/lsp/abstract_launcher.ts +++ /dev/null @@ -1,207 +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 { ResponseError } from 'vscode-jsonrpc'; -import { LanguageServerStartFailed } from '../../common/lsp_error_codes'; -import { Logger } from '../log'; -import { ServerOptions } from '../server_options'; -import { LoggerFactory } from '../utils/log_factory'; -import { ILanguageServerLauncher } from './language_server_launcher'; -import { LanguageServerProxy } from './proxy'; -import { RequestExpander } from './request_expander'; -import { ControlledProgram } from './process/controlled_program'; - -let seqNo = 1; - -export abstract class AbstractLauncher implements ILanguageServerLauncher { - running: boolean = false; - private currentPid: number = -1; - private child: ControlledProgram | null = null; - private startTime: number = -1; - private proxyConnected: boolean = false; - protected readonly log: Logger; - private spawnTimes: number = 0; - private launchReject?: (reason?: any) => void; - launchFailed: boolean = false; - protected constructor( - public readonly name: string, - public readonly targetHost: string, - public readonly options: ServerOptions, - public readonly loggerFactory: LoggerFactory - ) { - this.log = this.loggerFactory.getLogger([`${seqNo++}`, `${this.name}`, 'code']); - } - - public async launch(builtinWorkspace: boolean, maxWorkspace: number) { - const port = await this.getPort(); - const log: Logger = this.log; - const proxy = new LanguageServerProxy(port, this.targetHost, log); - if (this.options.lsp.detach) { - log.debug('Detach mode, expected language server launch externally'); - proxy.onConnected(() => { - this.running = true; - // reset spawn times - this.spawnTimes = 0; - }); - proxy.onDisconnected(() => { - this.running = false; - if (!proxy.isClosed) { - log.debug(`${this.name} language server disconnected, reconnecting`); - setTimeout(() => this.reconnect(proxy), 1000); - } - }); - } else { - const child = await this.doSpawnProcess(port, log); - this.onProcessExit(child, () => { - if (!proxy.isClosed) this.reconnect(proxy); - }); - proxy.onDisconnected(async () => { - this.proxyConnected = false; - if (!proxy.isClosed) { - log.debug('proxy disconnected, reconnecting'); - setTimeout(async () => { - await this.reconnect(proxy, child); - }, 1000); - } else if (this.child) { - log.info('proxy closed, kill process'); - await this.killProcess(child); - } - }); - this.child = child; - } - proxy.onExit(() => { - log.debug('proxy exited, is the program running? ' + this.running); - if (this.child && this.running) { - const p = this.child!; - this.killProcess(p); - } - }); - this.startConnect(proxy); - await new Promise((resolve, reject) => { - proxy.onConnected(() => { - this.proxyConnected = true; - // reset spawn times - this.spawnTimes = 0; - resolve(); - }); - this.launchReject = err => { - log.debug('launch error ' + err); - proxy.exit().catch(this.log.debug); - reject(err); - }; - }); - - return this.createExpander(proxy, builtinWorkspace, maxWorkspace); - } - - private onProcessExit(child: ControlledProgram, reconnectFn: () => void) { - const pid = child.pid; - child.onExit(() => { - if (this.currentPid === pid) { - this.running = false; - // if the process exited before proxy connected, then we reconnect - if (!this.proxyConnected) { - reconnectFn(); - } - } - }); - } - - /** - * proxy should be connected within this timeout, otherwise we reconnect. - */ - protected startupTimeout = 10000; - - protected maxRespawn = 3; - - /** - * try reconnect the proxy when disconnected - */ - public async reconnect(proxy: LanguageServerProxy, child?: ControlledProgram) { - this.log.debug('reconnecting'); - if (this.options.lsp.detach) { - this.startConnect(proxy); - } else { - const processExpired = () => Date.now() - this.startTime > this.startupTimeout; - if (child && !child.killed() && !processExpired()) { - this.startConnect(proxy); - } else { - if (this.spawnTimes < this.maxRespawn) { - if (child && this.running) { - this.log.debug('killing the old program.'); - await this.killProcess(child); - } - const port = await this.getPort(); - proxy.changePort(port); - this.child = await this.doSpawnProcess(port, this.log); - this.onProcessExit(this.child, () => this.reconnect(proxy, child)); - this.startConnect(proxy); - } else { - const ServerStartFailed = new ResponseError( - LanguageServerStartFailed, - 'Launch language server failed.' - ); - this.launchReject!(ServerStartFailed); - this.launchFailed = true; - proxy.setError(ServerStartFailed); - this.log.warn(`spawned program ${this.spawnTimes} times, mark this proxy unusable.`); - } - } - } - } - - private async doSpawnProcess(port: number, log: Logger): Promise { - this.log.debug('start program'); - const child = await this.spawnProcess(port, log); - this.currentPid = child.pid; - this.spawnTimes += 1; - this.startTime = Date.now(); - this.running = true; - return child; - } - - abstract async getPort(): Promise; - - startConnect(proxy: LanguageServerProxy) { - proxy.connect(); - } - - /** - * await for proxy connected, create a request expander - * @param proxy - * @param builtinWorkspace - * @param maxWorkspace - */ - abstract createExpander( - proxy: LanguageServerProxy, - builtinWorkspace: boolean, - maxWorkspace: number - ): RequestExpander; - - abstract async spawnProcess(port: number, log: Logger): Promise; - - protected killProcess(child: ControlledProgram) { - if (!child.killed()) { - return new Promise((resolve, reject) => { - // if not killed within 1s - const t = setTimeout(reject, 1000); - child.onExit(() => { - clearTimeout(t); - resolve(true); - }); - child.kill(false); - }) - .catch(() => { - // force kill - child.kill(true); - return child.killed(); - }) - .finally(() => { - if (this.currentPid === child.pid) this.running = false; - }); - } - } -} diff --git a/x-pack/legacy/plugins/code/server/lsp/controller.test.ts b/x-pack/legacy/plugins/code/server/lsp/controller.test.ts deleted file mode 100644 index 212381b36ca6e..0000000000000 --- a/x-pack/legacy/plugins/code/server/lsp/controller.test.ts +++ /dev/null @@ -1,166 +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 fs from 'fs'; -import * as os from 'os'; -import path from 'path'; -import rimraf from 'rimraf'; -import sinon from 'sinon'; - -import { LanguageServerStatus } from '../../common/language_server'; -import { LspRequest } from '../../model'; -import { RepositoryConfigController } from '../repository_config_controller'; -import { ServerOptions } from '../server_options'; -import { ConsoleLoggerFactory } from '../utils/console_logger_factory'; -import { LanguageServerController } from './controller'; -import { InstallManager } from './install_manager'; -import { ILanguageServerLauncher } from './language_server_launcher'; -import { JAVA, LanguageServerDefinition, TYPESCRIPT } from './language_servers'; -import { ILanguageServerHandler } from './proxy'; -import { createTestHapiServer } from '../test_utils'; - -const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'code_test')); -const workspaceDir = path.join(baseDir, 'workspace'); - -// @ts-ignore -const options: ServerOptions = sinon.createStubInstance(ServerOptions); -// @ts-ignore -options.lsp = { - TypeScript: { - enabled: true, - }, - Java: { - enabled: true, - }, - Go: { - enabled: true, - }, - Ctags: { - enabled: true, - }, - detach: false, -}; -// @ts-ignore -options.maxWorkspace = 2; -const server = createTestHapiServer(); -const installManager = new InstallManager(server, options); -// @ts-ignore -installManager.status = (def: LanguageServerDefinition) => { - return LanguageServerStatus.READY; -}; - -const repoConfigController = sinon.createStubInstance(RepositoryConfigController); -// @ts-ignore -repoConfigController.isLanguageDisabled = (uri: string, lang: string) => { - return Promise.resolve(false); -}; - -const launcherSpy = sinon.stub(); - -class LauncherStub implements ILanguageServerLauncher { - public get running(): boolean { - return launcherSpy.called; - } - - public launch( - builtinWorkspace: boolean, - maxWorkspace: number, - installationPath?: string - ): Promise { - return Promise.resolve(launcherSpy(builtinWorkspace, maxWorkspace, installationPath)); - } - - launchFailed: boolean = false; -} - -TYPESCRIPT.launcher = LauncherStub; -JAVA.launcher = LauncherStub; - -let controller: typeof LanguageServerController; - -beforeAll(() => { - fs.mkdirSync(workspaceDir, { recursive: true }); -}); -beforeEach(async () => { - sinon.reset(); - const handler: ILanguageServerHandler = { - handleRequest(request: LspRequest): any { - return {}; - }, - exit(): any { - return {}; - }, - unloadWorkspace(_: string): any { - return {}; - }, - }; - launcherSpy.returns(handler); - controller = new LanguageServerController( - options, - '127.0.0.1', - // @ts-ignore - installManager, - new ConsoleLoggerFactory(), - // @ts-ignore - repoConfigController - ); -}); -afterAll(() => { - rimraf.sync(baseDir); -}); - -function mockRequest(repo: string, file: string) { - const repoPath = path.join(workspaceDir, repo); - fs.mkdirSync(repoPath, { recursive: true }); - return { - method: 'request', - params: [], - workspacePath: repoPath, - timeoutForInitializeMs: 100, - resolvedFilePath: path.join(repoPath, file), - }; -} - -test('controller should launch a lang server', async () => { - const request = mockRequest('repo1', 'test.ts'); - // @ts-ignore - await controller.handleRequest(request); - expect(launcherSpy.calledOnce).toBeTruthy(); -}); - -test('java-lang-server support should only be launched exactly once', async () => { - const request1 = mockRequest('repo1', 'Test.java'); - const request2 = mockRequest('repo2', 'Test.java'); - // @ts-ignore - const p1 = controller.handleRequest(request1); - // @ts-ignore - const p2 = controller.handleRequest(request2); - await Promise.all([p1, p2]); - expect(launcherSpy.calledOnce).toBeTruthy(); -}); - -test('should launch 2 ts-lang-server for different repo', async () => { - const request1 = mockRequest('repo1', 'test.ts'); - const request2 = mockRequest('repo2', 'test.ts'); - // @ts-ignore - const p1 = controller.handleRequest(request1); - // @ts-ignore - const p2 = controller.handleRequest(request2); - await Promise.all([p1, p2]); - expect(launcherSpy.calledTwice).toBeTruthy(); -}); - -test('should only exactly 1 ts-lang-server for the same repo', async () => { - const request1 = mockRequest('repo1', 'test.ts'); - const request2 = mockRequest('repo1', 'test.ts'); - // @ts-ignore - const p1 = controller.handleRequest(request1); - // @ts-ignore - const p2 = controller.handleRequest(request2); - await Promise.all([p1, p2]); - expect(launcherSpy.calledOnce).toBeTruthy(); - expect(launcherSpy.calledTwice).toBe(false); -}); diff --git a/x-pack/legacy/plugins/code/server/lsp/controller.ts b/x-pack/legacy/plugins/code/server/lsp/controller.ts deleted file mode 100644 index 7c2e1acb8daf2..0000000000000 --- a/x-pack/legacy/plugins/code/server/lsp/controller.ts +++ /dev/null @@ -1,307 +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 fs from 'fs'; -import { ResponseError } from 'vscode-jsonrpc'; -import { ResponseMessage } from 'vscode-jsonrpc/lib/messages'; -import { LanguageServerStatus } from '../../common/language_server'; -import { - LanguageDisabled, - LanguageServerNotInstalled, - UnknownErrorCode, - UnknownFileLanguage, -} from '../../common/lsp_error_codes'; -import { LspRequest } from '../../model'; -import { Logger } from '../log'; -import { RepositoryConfigController } from '../repository_config_controller'; -import { ServerOptions } from '../server_options'; -import { detectLanguage } from '../utils/detect_language'; -import { LoggerFactory } from '../utils/log_factory'; -import { InstallManager } from './install_manager'; -import { ILanguageServerLauncher } from './language_server_launcher'; -import { enabledLanguageServers, LanguageServerDefinition } from './language_servers'; -import { ILanguageServerHandler } from './proxy'; -import { WorkspaceStatus } from './request_expander'; - -export interface LanguageServerHandlerMap { - [workspaceUri: string]: Promise; -} - -interface LanguageServerData { - definition: LanguageServerDefinition; - builtinWorkspaceFolders: boolean; - maxWorkspace: number; - languages: string[]; - launcher: ILanguageServerLauncher; - languageServerHandlers?: Promise | LanguageServerHandlerMap; -} - -/** - * Manage different LSP servers and forward request to different LSP using LanguageServerProxy, currently - * we just use forward request to all the LSP servers we are running. - */ -export class LanguageServerController implements ILanguageServerHandler { - // a list of support language servers - private readonly languageServers: LanguageServerData[]; - // a { lang -> server } map from above list - private readonly languageServerMap: { [lang: string]: LanguageServerData[] }; - private log: Logger; - - constructor( - public readonly options: ServerOptions, - public readonly targetHost: string, - public readonly installManager: InstallManager, - public readonly loggerFactory: LoggerFactory, - public readonly repoConfigController: RepositoryConfigController - ) { - this.log = loggerFactory.getLogger([]); - this.languageServers = enabledLanguageServers(options).map(def => ({ - definition: def, - builtinWorkspaceFolders: def.builtinWorkspaceFolders, - languages: def.languages, - maxWorkspace: options.maxWorkspace, - launcher: new def.launcher( - this.targetHost, - options, - loggerFactory, - this.installManager.installationPath(def)! - ), - })); - const add2map = ( - map: { [lang: string]: LanguageServerData[] }, - lang: string, - ls: LanguageServerData - ) => { - const arr = map[lang] || []; - arr.push(ls); - map[lang] = arr.sort((a, b) => b.definition.priority - a.definition.priority); - }; - this.languageServerMap = this.languageServers.reduce( - (map, ls) => { - ls.languages.forEach(lang => add2map(map, lang, ls)); - map[ls.definition.name] = [ls]; - return map; - }, - {} as { [lang: string]: LanguageServerData[] } - ); - } - - public async handleRequest(request: LspRequest): Promise { - const file = request.resolvedFilePath; - if (file) { - // #todo add test for this - const lang = await detectLanguage(file.replace('file://', '')); - if (await this.repoConfigController.isLanguageDisabled(request.documentUri!, lang)) { - throw new ResponseError(LanguageDisabled, `language disabled for the file`); - } - return await this.dispatchRequest(lang, request); - } else { - throw new ResponseError(UnknownErrorCode, `can't detect language without a file`); - } - } - - public async dispatchRequest(lang: string, request: LspRequest): Promise { - if (lang) { - const ls = this.findLanguageServer(lang); - if (ls.builtinWorkspaceFolders) { - if (!ls.languageServerHandlers && !ls.launcher.running) { - ls.languageServerHandlers = ls.launcher.launch( - ls.builtinWorkspaceFolders, - ls.maxWorkspace - ); - } - const handler = await (ls.languageServerHandlers as Promise); - return await handler.handleRequest(request); - } else { - const handler = await this.findOrCreateHandler(ls, request); - handler.lastAccess = Date.now(); - return await handler.handleRequest(request); - } - } else { - throw new ResponseError( - UnknownFileLanguage, - `can't detect language from file:${request.resolvedFilePath}` - ); - } - } - - /** - * shutdown all language servers - */ - public async exit() { - for (const ls of this.languageServers) { - if (ls.languageServerHandlers) { - if (ls.builtinWorkspaceFolders) { - if (ls.languageServerHandlers) { - try { - const h = await (ls.languageServerHandlers as Promise); - await h.exit(); - } catch (e) { - // expected error because of handler launch failed - } - } - } else { - const handlers = ls.languageServerHandlers as LanguageServerHandlerMap; - for (const handlerPromise of Object.values(handlers)) { - const handler = await handlerPromise; - await handler.exit(); - } - } - } - } - } - - public async launchServers() { - for (const ls of this.languageServers) { - const installed = this.installManager.status(ls.definition) === LanguageServerStatus.READY; - // for those language server has builtin workspace support, we can launch them during kibana startup - if (installed && ls.builtinWorkspaceFolders) { - try { - ls.languageServerHandlers = ls.launcher.launch(true, ls.maxWorkspace); - } catch (e) { - this.log.error(e); - } - } - } - } - - public async unloadWorkspace(workspaceDir: string) { - for (const languageServer of this.languageServers) { - if (languageServer.languageServerHandlers) { - if (languageServer.builtinWorkspaceFolders) { - try { - const handler = await (languageServer.languageServerHandlers as Promise< - ILanguageServerHandler - >); - await handler.unloadWorkspace(workspaceDir); - } catch (err) { - // expected error because of handler launch failed - } - } else { - const handlers = languageServer.languageServerHandlers as LanguageServerHandlerMap; - const realPath = fs.realpathSync(workspaceDir); - const handler = handlers[realPath]; - if (handler) { - await (await handler).unloadWorkspace(realPath); - delete handlers[realPath]; - } - } - } - } - } - - public status(def: LanguageServerDefinition): LanguageServerStatus { - const status = this.installManager.status(def); - // installed, but is it running? - if (status === LanguageServerStatus.READY) { - const ls = this.languageServers.find(d => d.definition === def); - if (ls && ls.launcher.launchFailed) { - return LanguageServerStatus.LAUNCH_FAILED; - } - if (ls && ls.launcher.running) { - return LanguageServerStatus.RUNNING; - } - } - return status; - } - - public getLanguageServerDef(lang: string): LanguageServerDefinition[] { - const data = this.languageServerMap[lang]; - if (data) { - return data.map(d => d.definition); - } - return []; - } - - private async findOrCreateHandler( - languageServer: LanguageServerData, - request: LspRequest - ): Promise { - let handlers: LanguageServerHandlerMap; - if (languageServer.languageServerHandlers) { - handlers = languageServer.languageServerHandlers as LanguageServerHandlerMap; - } else { - handlers = languageServer.languageServerHandlers = {}; - } - if (!request.workspacePath) { - throw new ResponseError(UnknownErrorCode, `no workspace in request?`); - } - const realPath = fs.realpathSync(request.workspacePath); - let handler = handlers[realPath]; - if (handler) { - return handler; - } else { - const maxWorkspace = languageServer.maxWorkspace; - const handlerArray = Object.entries(handlers); - if (handlerArray.length < maxWorkspace) { - handler = languageServer.launcher.launch( - languageServer.builtinWorkspaceFolders, - maxWorkspace - ); - handlers[realPath] = handler; - return handler; - } else { - let [oldestWorkspace, oldestHandler] = handlerArray[0]; - for (const e of handlerArray) { - const [ws, handlePromise] = e; - const h = await handlePromise; - const oldestAccess = (await oldestHandler).lastAccess!; - if (h.lastAccess! < oldestAccess!) { - oldestWorkspace = ws; - oldestHandler = handlePromise; - } - } - delete handlers[oldestWorkspace]; - handlers[request.workspacePath] = oldestHandler; - return oldestHandler; - } - } - } - - private findLanguageServer(lang: string) { - const lsArr = this.languageServerMap[lang]; - if (lsArr) { - const ls = lsArr.find( - d => this.installManager.status(d.definition) !== LanguageServerStatus.NOT_INSTALLED - ); - if (!this.options.lsp.detach && ls === undefined) { - throw new ResponseError( - LanguageServerNotInstalled, - `language server ${lang} not installed` - ); - } else { - return ls!; - } - } else { - throw new ResponseError(UnknownFileLanguage, `unsupported language ${lang}`); - } - } - - public async initializeState(workspacePath: string) { - const result: { [lang: string]: WorkspaceStatus } = {}; - for (const languageServer of this.languageServers) { - if (languageServer.languageServerHandlers) { - if (languageServer.builtinWorkspaceFolders) { - const handler = await (languageServer.languageServerHandlers as Promise< - ILanguageServerHandler - >); - result[languageServer.definition.name] = (await handler.initializeState!( - workspacePath - )) as WorkspaceStatus; - } else { - const handlers = languageServer.languageServerHandlers as LanguageServerHandlerMap; - const realPath = fs.realpathSync(workspacePath); - const handler = handlers[realPath]; - if (handler) { - result[languageServer.definition.name] = (await handler).initializeState!( - workspacePath - ) as WorkspaceStatus; - } - } - } - } - return result; - } -} diff --git a/x-pack/legacy/plugins/code/server/lsp/ctags_launcher.ts b/x-pack/legacy/plugins/code/server/lsp/ctags_launcher.ts deleted file mode 100644 index 97c4bc79cff71..0000000000000 --- a/x-pack/legacy/plugins/code/server/lsp/ctags_launcher.ts +++ /dev/null @@ -1,59 +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 getPort from 'get-port'; -import { ServerOptions } from '../server_options'; -import { LoggerFactory } from '../utils/log_factory'; -import { LanguageServerProxy } from './proxy'; -import { Logger } from '../log'; -import { RequestExpander } from './request_expander'; -import { AbstractLauncher } from './abstract_launcher'; -import { EmbedCtagServer } from './process/embed_ctag_server'; - -const CTAGS_LANG_DETACH_PORT = 2092; -export class CtagsLauncher extends AbstractLauncher { - private isRunning: boolean = false; - private embed: EmbedCtagServer | null = null; - constructor( - public readonly targetHost: string, - public readonly options: ServerOptions, - public readonly loggerFactory: LoggerFactory - ) { - super('ctags', targetHost, options, loggerFactory); - } - public get running(): boolean { - return this.isRunning; - } - - createExpander( - proxy: LanguageServerProxy, - builtinWorkspace: boolean, - maxWorkspace: number - ): RequestExpander { - return new RequestExpander(proxy, builtinWorkspace, maxWorkspace, this.options, {}, this.log); - } - - startConnect(proxy: LanguageServerProxy) { - proxy.startServerConnection(); - if (this.embed) { - this.embed.start().catch(err => this.log.error(err)); - } - } - - async getPort(): Promise { - if (!this.options.lsp.detach) { - return await getPort(); - } - return CTAGS_LANG_DETACH_PORT; - } - - async spawnProcess(port: number, log: Logger) { - if (!this.embed) { - this.embed = new EmbedCtagServer(port, log); - } - return this.embed; - } -} diff --git a/x-pack/legacy/plugins/code/server/lsp/go_launcher.ts b/x-pack/legacy/plugins/code/server/lsp/go_launcher.ts deleted file mode 100644 index 01207e9e60344..0000000000000 --- a/x-pack/legacy/plugins/code/server/lsp/go_launcher.ts +++ /dev/null @@ -1,149 +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 paths from '@elastic/simple-git/dist/util/paths'; -import { spawn } from 'child_process'; -import fs from 'fs'; -import getPort from 'get-port'; -import * as glob from 'glob'; -import { platform as getOsPlatform } from 'os'; -import path from 'path'; -import { MarkupKind } from 'vscode-languageserver-protocol'; -import { Logger } from '../log'; -import { ServerOptions } from '../server_options'; -import { LoggerFactory } from '../utils/log_factory'; -import { AbstractLauncher } from './abstract_launcher'; -import { ExternalProgram } from './process/external_program'; -import { LanguageServerProxy } from './proxy'; -import { InitializeOptions, RequestExpander } from './request_expander'; - -const GO_LANG_DETACH_PORT = 2091; - -export class GoServerLauncher extends AbstractLauncher { - constructor( - public readonly targetHost: string, - public readonly options: ServerOptions, - public readonly loggerFactory: LoggerFactory, - public readonly installationPath: string - ) { - super('go', targetHost, options, loggerFactory); - } - - createExpander( - proxy: LanguageServerProxy, - builtinWorkspace: boolean, - maxWorkspace: number - ): RequestExpander { - return new RequestExpander( - proxy, - builtinWorkspace, - maxWorkspace, - this.options, - { - initialOptions: { - installGoDependency: this.options.security.installGoDependency, - }, - clientCapabilities: { - textDocument: { - hover: { - contentFormat: [MarkupKind.Markdown, MarkupKind.PlainText], - }, - }, - }, - } as InitializeOptions, - this.log - ); - } - - async startConnect(proxy: LanguageServerProxy) { - await proxy.connect(); - } - - async getPort() { - if (!this.options.lsp.detach) { - return await getPort(); - } - return GO_LANG_DETACH_PORT; - } - - private async getBundledGoToolchain(installationPath: string, log: Logger) { - const GoToolchain = glob.sync('**/go/**', { - cwd: installationPath, - }); - if (!GoToolchain.length) { - return undefined; - } - return path.resolve(installationPath, GoToolchain[0]); - } - - async spawnProcess(port: number, log: Logger) { - const launchersFound = glob.sync( - process.platform === 'win32' ? 'go-langserver.exe' : 'go-langserver', - { - cwd: this.installationPath, - } - ); - if (!launchersFound.length) { - throw new Error('Cannot find executable go language server'); - } - - const goToolchain = await this.getBundledGoToolchain(this.installationPath, log); - if (!goToolchain) { - throw new Error('Cannot find go toolchain in bundle installation'); - } - - const goRoot = goToolchain; - const goHome = path.resolve(goToolchain, 'bin'); - const goPath = this.options.goPath; - if (!fs.existsSync(goPath)) { - fs.mkdirSync(goPath); - } - const goCache = path.resolve(goPath, '.cache'); - - const langserverRelatedEnv: { [name: string]: string } = { - GOROOT: goRoot, - GOPATH: goPath, - GOCACHE: goCache, - CGO_ENABLED: '0', - }; - - // Always prefer the bundled git. - const platform = getOsPlatform(); - const git = paths(platform); - const gitPath = path.dirname(git.binPath); - if (platform !== 'win32') { - langserverRelatedEnv.PREFIX = git.nativeDir; - if (platform === 'linux') { - langserverRelatedEnv.GIT_SSL_CAINFO = path.join(git.nativeDir, 'ssl/cacert.pem'); - } - } - - const params: string[] = ['-port=' + port.toString()]; - const golsp = path.resolve(this.installationPath, launchersFound[0]); - const env = Object.create(process.env); - env.PATH = gitPath + path.delimiter + goHome + path.delimiter + env.PATH; - const p = spawn(golsp, params, { - detached: false, - stdio: 'pipe', - env: { - ...env, - CLIENT_HOST: '127.0.0.1', - CLIENT_PORT: port.toString(), - ...langserverRelatedEnv, - }, - }); - p.stdout.on('data', data => { - log.stdout(data.toString()); - }); - p.stderr.on('data', data => { - log.stderr(data.toString()); - }); - log.info( - `Launch Go Language Server at port ${port.toString()}, pid:${p.pid}, GOROOT:${goRoot}` - ); - return new ExternalProgram(p, this.options, log); - } -} diff --git a/x-pack/legacy/plugins/code/server/lsp/install_manager.test.ts b/x-pack/legacy/plugins/code/server/lsp/install_manager.test.ts deleted file mode 100644 index 6aa80899a7008..0000000000000 --- a/x-pack/legacy/plugins/code/server/lsp/install_manager.test.ts +++ /dev/null @@ -1,68 +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. - */ -/* eslint-disable no-console */ - -import fs from 'fs'; -import { Server } from 'hapi'; -import os from 'os'; -import path from 'path'; -import rimraf from 'rimraf'; - -import { LanguageServers } from './language_servers'; -import { InstallManager } from './install_manager'; -import { ServerOptions } from '../server_options'; -import { LanguageServerStatus } from '../../common/language_server'; -import { InstallationType } from '../../common/installation'; -import { ServerFacade } from '../..'; - -const LANG_SERVER_NAME = 'Java'; -const langSrvDef = LanguageServers.find(l => l.name === LANG_SERVER_NAME)!; - -const fakeTestDir = path.join(os.tmpdir(), 'foo-'); - -const options: ServerOptions = {} as ServerOptions; - -const server: ServerFacade = new Server(); -server.config = () => { - return { - get(key: string): any { - if (key === 'pkg.version') { - return '8.0.0'; - } - }, - has(key: string): boolean { - return key === 'pkg.version'; - }, - }; -}; - -const manager = new InstallManager(server, options); - -beforeAll(() => { - fs.mkdirSync(fakeTestDir); -}); - -afterAll(() => { - rimraf.sync(fakeTestDir); -}); - -test('install language server by plugin', async () => { - langSrvDef.installationType = InstallationType.Plugin; - expect(manager.status(langSrvDef)).toBe(LanguageServerStatus.NOT_INSTALLED); - const testDir = path.join(fakeTestDir, 'test_plugin'); - fs.mkdirSync(testDir); - const pluginName = langSrvDef.installationPluginName as string; - // @ts-ignore - server.plugins = { - [pluginName]: { - install: { - path: testDir, - }, - }, - }; - expect(manager.status(langSrvDef)).toBe(LanguageServerStatus.READY); - expect(manager.installationPath(langSrvDef)).toBe(testDir); -}); diff --git a/x-pack/legacy/plugins/code/server/lsp/install_manager.ts b/x-pack/legacy/plugins/code/server/lsp/install_manager.ts deleted file mode 100644 index 1f90939899931..0000000000000 --- a/x-pack/legacy/plugins/code/server/lsp/install_manager.ts +++ /dev/null @@ -1,44 +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 fs from 'fs'; - -import { InstallationType } from '../../common/installation'; -import { LanguageServerStatus } from '../../common/language_server'; -import { ServerOptions } from '../server_options'; -import { LanguageServerDefinition } from './language_servers'; -import { ServerFacade } from '../..'; - -export class InstallManager { - constructor(public readonly server: ServerFacade, public readonly serverOptions: ServerOptions) {} - - public status(def: LanguageServerDefinition): LanguageServerStatus { - if (def.installationType === InstallationType.Embed) { - return LanguageServerStatus.READY; - } - // @ts-ignore - const plugin = this.server.plugins[def.installationPluginName!]; - if (plugin) { - const pluginPath = plugin.install.path; - if (fs.existsSync(pluginPath)) { - return LanguageServerStatus.READY; - } - } - return LanguageServerStatus.NOT_INSTALLED; - } - - public installationPath(def: LanguageServerDefinition): string | undefined { - if (def.installationType === InstallationType.Embed) { - return def.embedPath!; - } - // @ts-ignore - const plugin: any = this.server.plugins[def.installationPluginName]; - if (plugin) { - return plugin.install.path; - } - return undefined; - } -} diff --git a/x-pack/legacy/plugins/code/server/lsp/java_launcher.ts b/x-pack/legacy/plugins/code/server/lsp/java_launcher.ts deleted file mode 100644 index ea509a06b07d5..0000000000000 --- a/x-pack/legacy/plugins/code/server/lsp/java_launcher.ts +++ /dev/null @@ -1,280 +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 { execFile, spawn } from 'child_process'; -import { existsSync, mkdirSync } from 'fs'; -import getPort from 'get-port'; -import * as glob from 'glob'; -import { platform as getOsPlatform } from 'os'; -import path from 'path'; -import { Logger } from '../log'; -import { ServerOptions } from '../server_options'; -import { LoggerFactory } from '../utils/log_factory'; -import { AbstractLauncher } from './abstract_launcher'; -import { LanguageServerProxy } from './proxy'; -import { InitializeOptions, RequestExpander } from './request_expander'; -import { ExternalProgram } from './process/external_program'; - -const JAVA_LANG_DETACH_PORT = 2090; - -export class JavaLauncher extends AbstractLauncher { - private needModuleArguments: boolean = true; - private readonly gradleHomeFolder = '.gradle'; - constructor( - public readonly targetHost: string, - public readonly options: ServerOptions, - public readonly loggerFactory: LoggerFactory, - public readonly installationPath: string - ) { - super('java', targetHost, options, loggerFactory); - } - - createExpander(proxy: LanguageServerProxy, builtinWorkspace: boolean, maxWorkspace: number) { - return new RequestExpander( - proxy, - builtinWorkspace, - maxWorkspace, - this.options, - { - initialOptions: { - settings: { - 'java.import.gradle.enabled': this.options.security.enableGradleImport, - 'java.import.maven.enabled': this.options.security.enableMavenImport, - 'java.autobuild.enabled': false, - 'java.import.gradle.home': path.resolve( - this.options.jdtWorkspacePath, - this.gradleHomeFolder - ), - 'java.configuration.maven.userSettings': path.resolve( - this.installationPath, - 'settings/settings.xml' - ), - }, - }, - clientCapabilities: { - textDocument: { - documentSymbol: { - hierarchicalDocumentSymbolSupport: true, - }, - }, - }, - } as InitializeOptions, - this.log - ); - } - - startConnect(proxy: LanguageServerProxy) { - proxy.startServerConnection(); - } - - async getPort(): Promise { - if (!this.options.lsp.detach) { - return await getPort(); - } - return JAVA_LANG_DETACH_PORT; - } - - private async getJavaHome(installationPath: string, log: Logger) { - function findJDK(platform: string) { - const JDKFound = glob.sync(`**/jdks/*${platform}/jdk-*`, { - cwd: installationPath, - }); - if (!JDKFound.length) { - log.error('Cannot find Java Home in Bundle installation for ' + platform); - return undefined; - } - return path.resolve(installationPath, JDKFound[0]); - } - - let bundledJavaHome; - - // detect platform - const osPlatform = getOsPlatform(); - switch (osPlatform) { - case 'darwin': - bundledJavaHome = `${findJDK('osx')}/Contents/Home`; - break; - case 'win32': - bundledJavaHome = `${findJDK('windows')}`; - break; - case 'freebsd': - case 'linux': - bundledJavaHome = `${findJDK('linux')}`; - break; - default: - log.error('No Bundle JDK defined ' + osPlatform); - } - - if (this.getSystemJavaHome()) { - const javaHomePath = this.getSystemJavaHome(); - const javaVersion = await this.getJavaVersion(javaHomePath); - if (javaVersion > 8) { - // for JDK's versiob > 8, we need extra arguments as default - return javaHomePath; - } else if (javaVersion === 8) { - // JDK's version = 8, needn't extra arguments - this.needModuleArguments = false; - return javaHomePath; - } else { - // JDK's version < 8, use bundled JDK instead, whose version > 8, so need extra arguments as default - } - } - - return bundledJavaHome; - } - - async spawnProcess(port: number, log: Logger) { - const launchersFound = glob.sync('**/plugins/org.eclipse.equinox.launcher_*.jar', { - cwd: this.installationPath, - }); - if (!launchersFound.length) { - throw new Error('Cannot find language server jar file'); - } - - const javaHomePath = await this.getJavaHome(this.installationPath, log); - if (!javaHomePath) { - throw new Error('Cannot find Java Home'); - } - - const javaPath = path.resolve( - javaHomePath, - 'bin', - process.platform === 'win32' ? 'java.exe' : 'java' - ); - - const configPath = - process.platform === 'win32' - ? path.resolve(this.installationPath, 'repository/config_win') - : this.options.jdtConfigPath; - - const params: string[] = [ - '-Declipse.application=org.elastic.jdt.ls.core.id1', - '-Dosgi.bundles.defaultStartLevel=4', - '-Declipse.product=org.elastic.jdt.ls.core.product', - '-Dlog.level=ALL', - '-Dfile.encoding=utf8', - '-noverify', - '-Xmx4G', - '-jar', - path.resolve(this.installationPath, launchersFound[0]), - '-configuration', - configPath, - '-data', - this.options.jdtWorkspacePath, - ]; - - if (this.options.security.enableJavaSecurityManager) { - params.unshift( - '-Dorg.osgi.framework.security=osgi', - `-Djava.security.policy=${path.resolve(this.installationPath, 'all.policy')}` - ); - } - - if (this.needModuleArguments) { - params.push( - '--add-modules=ALL-SYSTEM', - '--add-opens', - 'java.base/java.util=ALL-UNNAMED', - '--add-opens', - 'java.base/java.lang=ALL-UNNAMED' - ); - } - - // Check if workspace exists before launching - if (!existsSync(this.options.jdtWorkspacePath)) { - mkdirSync(this.options.jdtWorkspacePath); - } - - const p = spawn(javaPath, params, { - cwd: this.options.jdtWorkspacePath, - detached: false, - stdio: 'pipe', - env: { - ...process.env, - CLIENT_HOST: '127.0.0.1', - CLIENT_PORT: port.toString(), - JAVA_HOME: javaHomePath, - EXTRA_WHITELIST_HOST: this.options.security.extraJavaRepositoryWhitelist.join(','), - }, - }); - p.stdout.on('data', data => { - log.stdout(data.toString()); - }); - p.stderr.on('data', data => { - log.stderr(data.toString()); - }); - log.info( - `Launch Java Language Server at port ${port.toString()}, pid:${ - p.pid - }, JAVA_HOME:${javaHomePath}` - ); - return new ExternalProgram(p, this.options, log); - } - - // TODO(pcxu): run /usr/libexec/java_home to get all java homes for macOS - private getSystemJavaHome(): string { - let javaHome = process.env.JDK_HOME; - if (!javaHome) { - javaHome = process.env.JAVA_HOME; - } - if (javaHome) { - javaHome = this.expandHomeDir(javaHome); - const JAVAC_FILENAME = 'javac' + (process.platform === 'win32' ? '.exe' : ''); - if (existsSync(javaHome) && existsSync(path.resolve(javaHome, 'bin', JAVAC_FILENAME))) { - return javaHome; - } - } - return ''; - } - - private getJavaVersion(javaHome: string): Promise { - return new Promise((resolve, reject) => { - execFile( - path.resolve(javaHome, 'bin', process.platform === 'win32' ? 'java.exe' : 'java'), - ['-version'], - {}, - (error, stdout, stderr) => { - const javaVersion = this.parseMajorVersion(stderr); - resolve(javaVersion); - } - ); - }); - } - - private parseMajorVersion(content: string): number { - let regexp = /version "(.*)"/g; - let match = regexp.exec(content); - if (!match) { - return 0; - } - let version = match[1]; - if (version.startsWith('1.')) { - version = version.substring(2); - } - - regexp = /\d+/g; - match = regexp.exec(version); - let javaVersion = 0; - if (match) { - javaVersion = parseInt(match[0], 10); - } - return javaVersion; - } - - private expandHomeDir(javaHome: string): string { - const homeDir = process.env[process.platform === 'win32' ? 'USERPROFILE' : 'HOME']; - if (!javaHome) { - return javaHome; - } - if (javaHome === '~') { - return homeDir!; - } - if (javaHome.slice(0, 2) !== '~/') { - return javaHome; - } - return path.join(homeDir!, javaHome.slice(2)); - } -} diff --git a/x-pack/legacy/plugins/code/server/lsp/language_server_launcher.test.ts b/x-pack/legacy/plugins/code/server/lsp/language_server_launcher.test.ts deleted file mode 100644 index 28fad5bbea60b..0000000000000 --- a/x-pack/legacy/plugins/code/server/lsp/language_server_launcher.test.ts +++ /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. - */ - -import fs from 'fs'; -import { ServerOptions } from '../server_options'; -import { createTestServerOption } from '../test_utils'; -import { ConsoleLoggerFactory } from '../utils/console_logger_factory'; -import { TYPESCRIPT } from './language_servers'; -import { TypescriptServerLauncher } from './ts_launcher'; - -jest.setTimeout(15000); - -// @ts-ignore -const options: ServerOptions = createTestServerOption(); - -beforeAll(async () => { - if (!fs.existsSync(options.workspacePath)) { - fs.mkdirSync(options.workspacePath, { recursive: true }); - fs.mkdirSync(options.jdtWorkspacePath, { recursive: true }); - } -}); - -function delay(seconds: number) { - return new Promise(resolve => { - setTimeout(() => resolve(), seconds * 1000); - }); -} - -test('typescript language server could be shutdown', async () => { - const tsLauncher = new TypescriptServerLauncher( - 'localhost', - options, - new ConsoleLoggerFactory(), - TYPESCRIPT.embedPath! - ); - const proxy = await tsLauncher.launch(true, 1); - expect(tsLauncher.running).toBeTruthy(); - await proxy.initialize(options.workspacePath); - await proxy.exit(); - await delay(3); - expect(tsLauncher.running).toBeFalsy(); -}); diff --git a/x-pack/legacy/plugins/code/server/lsp/language_server_launcher.ts b/x-pack/legacy/plugins/code/server/lsp/language_server_launcher.ts deleted file mode 100644 index 688f1f66f47d1..0000000000000 --- a/x-pack/legacy/plugins/code/server/lsp/language_server_launcher.ts +++ /dev/null @@ -1,22 +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 { ServerOptions } from '../server_options'; -import { LoggerFactory } from '../utils/log_factory'; -import { ILanguageServerHandler } from './proxy'; - -export interface ILanguageServerLauncher { - running: boolean; - launch(builtinWorkspace: boolean, maxWorkspace: number): Promise; - launchFailed: boolean; -} - -export type LauncherConstructor = new ( - targetHost: string, - options: ServerOptions, - loggerFactory: LoggerFactory, - installationPath: string -) => ILanguageServerLauncher; diff --git a/x-pack/legacy/plugins/code/server/lsp/language_servers.ts b/x-pack/legacy/plugins/code/server/lsp/language_servers.ts deleted file mode 100644 index 40b6a41d68c58..0000000000000 --- a/x-pack/legacy/plugins/code/server/lsp/language_servers.ts +++ /dev/null @@ -1,89 +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 { InstallationType } from '../../common/installation'; -import { CTAGS_SUPPORT_LANGS, LanguageServer } from '../../common/language_server'; -import { ServerOptions } from '../server_options'; -import { CtagsLauncher } from './ctags_launcher'; -import { GoServerLauncher } from './go_launcher'; -import { JavaLauncher } from './java_launcher'; -import { LauncherConstructor } from './language_server_launcher'; -import { TypescriptServerLauncher } from './ts_launcher'; - -export interface LanguageServerDefinition extends LanguageServer { - builtinWorkspaceFolders: boolean; - launcher: LauncherConstructor; - installationFolderName?: string; - downloadUrl?: (version: string, devMode?: boolean) => string; - embedPath?: string; - installationPluginName?: string; - priority: number; -} - -export const TYPESCRIPT: LanguageServerDefinition = { - name: 'TypeScript', - builtinWorkspaceFolders: false, - languages: ['typescript', 'javascript'], - launcher: TypescriptServerLauncher, - installationType: InstallationType.Embed, - embedPath: require.resolve('@elastic/javascript-typescript-langserver/lib/language-server.js'), - priority: 2, -}; -export const JAVA: LanguageServerDefinition = { - name: 'Java', - builtinWorkspaceFolders: true, - languages: ['java'], - launcher: JavaLauncher, - installationType: InstallationType.Plugin, - installationPluginName: 'java-langserver', - installationFolderName: 'jdt', - priority: 2, - downloadUrl: (version: string, devMode?: boolean) => - devMode! - ? `https://snapshots.elastic.co/downloads/kibana-plugins/java-langserver/java-langserver-${version}-SNAPSHOT-$OS.zip` - : `https://artifacts.elastic.co/downloads/kibana-plugins/java-langserver/java-langserver-${version}-$OS.zip`, -}; -export const GO: LanguageServerDefinition = { - name: 'Go', - builtinWorkspaceFolders: true, - languages: ['go'], - launcher: GoServerLauncher, - installationType: InstallationType.Plugin, - installationPluginName: 'go-langserver', - priority: 2, - installationFolderName: 'golsp', - downloadUrl: (version: string, devMode?: boolean) => - devMode! - ? `https://snapshots.elastic.co/downloads/kibana-plugins/go-langserver/go-langserver-${version}-SNAPSHOT-$OS.zip` - : `https://artifacts.elastic.co/downloads/kibana-plugins/go-langserver/go-langserver-${version}-$OS.zip`, -}; -export const CTAGS: LanguageServerDefinition = { - name: 'Ctags', - builtinWorkspaceFolders: true, - languages: CTAGS_SUPPORT_LANGS, - launcher: CtagsLauncher, - installationType: InstallationType.Embed, - embedPath: require.resolve('@elastic/ctags-langserver/lib/cli.js'), - priority: 1, -}; -export const LanguageServers: LanguageServerDefinition[] = [TYPESCRIPT, JAVA, GO, CTAGS]; -export const LanguageServersDeveloping: LanguageServerDefinition[] = []; - -export function enabledLanguageServers(serverOptions: ServerOptions) { - const devMode: boolean = serverOptions.devMode; - - function isEnabled(lang: LanguageServerDefinition, defaultEnabled: boolean) { - const name = lang.name; - // @ts-ignore - const enabled = serverOptions.lsp[name] && serverOptions.lsp[name].enabled; - return enabled === undefined ? defaultEnabled : enabled; - } - const results = LanguageServers.filter(lang => isEnabled(lang, true)); - if (devMode) { - return results.concat(LanguageServersDeveloping.filter(lang => isEnabled(lang, devMode))); - } - return results; -} diff --git a/x-pack/legacy/plugins/code/server/lsp/lsp_benchmark.ts b/x-pack/legacy/plugins/code/server/lsp/lsp_benchmark.ts deleted file mode 100644 index 8fbf3aab74798..0000000000000 --- a/x-pack/legacy/plugins/code/server/lsp/lsp_benchmark.ts +++ /dev/null @@ -1,81 +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 fs from 'fs'; -import path from 'path'; -import yaml from 'js-yaml'; -import { TestConfig, RequestType } from '../../model/test_config'; -import { TestRepoManager } from './test_repo_manager'; -import { LspTestRunner } from './lsp_test_runner'; - -jest.setTimeout(300000); - -let repoManger: TestRepoManager; -const resultFile = `benchmark_result_${Date.now()}.csv`; - -function sleep(ms: number) { - return new Promise(resolve => setTimeout(resolve, ms)); -} - -beforeAll(async () => { - const config: TestConfig = yaml.safeLoad(fs.readFileSync('test_config.yml', 'utf8')); - repoManger = new TestRepoManager(config); - await repoManger.importAllRepos(); -}); - -it('test Java lsp full', async () => { - const repo = repoManger.getRepo('java'); - const runner = new LspTestRunner(repo, RequestType.FULL, 10); - await runner.launchLspByLanguage(); - // sleep until jdt connection established - await sleep(3000); - await runner.sendRandomRequest(); - await runner.proxy!.exit(); - runner.dumpToCSV(resultFile); - expect(true); -}); - -it('test Java lsp hover', async () => { - const repo = repoManger.getRepo('java'); - const runner = new LspTestRunner(repo, RequestType.HOVER, 10); - await runner.launchLspByLanguage(); - // sleep until jdt connection established - await sleep(3000); - await runner.sendRandomRequest(); - await runner.proxy!.exit(); - runner.dumpToCSV(resultFile); - expect(true); -}); - -it('test ts lsp full', async () => { - const repo = repoManger.getRepo('ts'); - const runner = new LspTestRunner(repo, RequestType.FULL, 10); - await runner.launchLspByLanguage(); - await sleep(2000); - await runner.sendRandomRequest(); - await runner.proxy!.exit(); - runner.dumpToCSV(resultFile); - await sleep(2000); - expect(true); -}); - -it('test ts lsp hover', async () => { - const repo = repoManger.getRepo('ts'); - const runner = new LspTestRunner(repo, RequestType.HOVER, 10); - await runner.launchLspByLanguage(); - await sleep(3000); - await runner.sendRandomRequest(); - await runner.proxy!.exit(); - runner.dumpToCSV(resultFile); - await sleep(2000); - expect(true); -}); - -afterAll(async () => { - // eslint-disable-next-line no-console - console.log(`result file ${path.resolve(__dirname)}/${resultFile} was saved!`); - await repoManger.cleanAllRepos(); -}); diff --git a/x-pack/legacy/plugins/code/server/lsp/lsp_service.ts b/x-pack/legacy/plugins/code/server/lsp/lsp_service.ts deleted file mode 100644 index 88f9992f2d808..0000000000000 --- a/x-pack/legacy/plugins/code/server/lsp/lsp_service.ts +++ /dev/null @@ -1,98 +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 { ResponseMessage } from 'vscode-jsonrpc/lib/messages'; - -import { LanguageServerStatus } from '../../common/language_server'; -import { GitOperations } from '../git_operations'; -import { EsClient } from '../lib/esqueue'; -import { RepositoryConfigController } from '../repository_config_controller'; -import { ServerOptions } from '../server_options'; -import { LoggerFactory } from '../utils/log_factory'; -import { LanguageServerController } from './controller'; -import { InstallManager } from './install_manager'; -import { WorkspaceHandler } from './workspace_handler'; - -export class LspService { - public readonly controller: LanguageServerController; - public readonly workspaceHandler: WorkspaceHandler; - constructor( - targetHost: string, - serverOptions: ServerOptions, - gitOps: GitOperations, - client: EsClient, - installManager: InstallManager, - loggerFactory: LoggerFactory, - repoConfigController: RepositoryConfigController - ) { - this.workspaceHandler = new WorkspaceHandler( - gitOps, - serverOptions.workspacePath, - client, - loggerFactory - ); - this.controller = new LanguageServerController( - serverOptions, - targetHost, - installManager, - loggerFactory, - repoConfigController - ); - } - - /** - * send a lsp request to language server, will initiate the language server if needed - * @param method the method name - * @param params the request params - */ - public async sendRequest(method: string, params: any): Promise { - const request = { method, params }; - await this.workspaceHandler.handleRequest(request); - const response = await this.controller.handleRequest(request); - return this.workspaceHandler.handleResponse(request, response); - } - - public async launchServers() { - await this.controller.launchServers(); - } - - public async deleteWorkspace(repoUri: string) { - for (const path of await this.workspaceHandler.listWorkspaceFolders(repoUri)) { - await this.controller.unloadWorkspace(path); - } - await this.workspaceHandler.clearWorkspace(repoUri); - } - - /** - * shutdown all launched language servers - */ - public async shutdown() { - await this.controller.exit(); - } - - public supportLanguage(lang: string) { - return this.controller.getLanguageServerDef(lang).length > 0; - } - - public getLanguageSeverDef(lang: string) { - return this.controller.getLanguageServerDef(lang); - } - - public languageServerStatus(name: string): LanguageServerStatus { - const defs = this.controller.getLanguageServerDef(name); - if (defs.length > 0) { - const def = defs[0]; - return this.controller.status(def); - } else { - return LanguageServerStatus.NOT_INSTALLED; - } - } - - public async initializeState(repoUri: string, revision: string) { - const workspacePath = await this.workspaceHandler.revisionDir(repoUri, revision); - return await this.controller.initializeState(workspacePath); - } -} diff --git a/x-pack/legacy/plugins/code/server/lsp/lsp_test_runner.ts b/x-pack/legacy/plugins/code/server/lsp/lsp_test_runner.ts deleted file mode 100644 index 4dca98b646b85..0000000000000 --- a/x-pack/legacy/plugins/code/server/lsp/lsp_test_runner.ts +++ /dev/null @@ -1,254 +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. - */ - -/* eslint-disable no-console */ - -import fs from 'fs'; -// @ts-ignore -import * as sl from 'stats-lite'; -import _ from 'lodash'; -import papa from 'papaparse'; - -import { InstallManager } from './install_manager'; -import { JavaLauncher } from './java_launcher'; -import { JAVA, TYPESCRIPT } from './language_servers'; -import { RequestExpander } from './request_expander'; -import { TypescriptServerLauncher } from './ts_launcher'; -import { GitOperations } from '../git_operations'; -import { createTestServerOption } from '../test_utils'; -import { ConsoleLoggerFactory } from '../utils/console_logger_factory'; -import { LspRequest } from '../../model'; -import { Repo, RequestType } from '../../model/test_config'; - -const requestTypeMapping = new Map([ - [RequestType.FULL, 'full'], - [RequestType.HOVER, 'hover'], - [RequestType.INITIALIZE, 'initialize'], -]); - -interface Result { - repoName: string; - startTime: number; - numberOfRequests: number; - rps: number; - OK: number; - KO: number; - KORate: string; - latency_max: number; - latency_min: number; - latency_medium: number; - latency_95: number; - latency_99: number; - latency_avg: number; - latency_std_dev: number; -} - -const serverOptions = createTestServerOption(); - -export class LspTestRunner { - private repo: Repo; - public result: Result; - public proxy: RequestExpander | null; - private requestType: RequestType; - private times: number; - - constructor(repo: Repo, requestType: RequestType, times: number) { - this.repo = repo; - this.requestType = requestType; - this.times = times; - this.proxy = null; - this.result = { - repoName: `${repo.url}`, - startTime: 0, - numberOfRequests: times, - rps: 0, - OK: 0, - KO: 0, - KORate: '', - latency_max: 0, - latency_min: 0, - latency_medium: 0, - latency_95: 0, - latency_99: 0, - latency_avg: 0, - latency_std_dev: 0, - }; - if (!fs.existsSync(serverOptions.workspacePath)) { - fs.mkdirSync(serverOptions.workspacePath); - } - } - - public async sendRandomRequest() { - const repoPath: string = this.repo.path; - const files = await this.getAllFile(); - const randomFile = files[Math.floor(Math.random() * files.length)]; - await this.proxy!.initialize(repoPath); - switch (this.requestType) { - case RequestType.HOVER: { - const req: LspRequest = { - method: 'textDocument/hover', - params: { - textDocument: { - uri: `file://${this.repo.path}/${randomFile}`, - }, - position: this.randomizePosition(), - }, - }; - await this.launchRequest(req); - break; - } - case RequestType.INITIALIZE: { - this.proxy!.initialize(repoPath); - break; - } - case RequestType.FULL: { - const req: LspRequest = { - method: 'textDocument/full', - params: { - textDocument: { - uri: `file://${this.repo.path}/${randomFile}`, - }, - reference: false, - }, - }; - await this.launchRequest(req); - break; - } - default: { - console.error('Unknown request type!'); - break; - } - } - } - - private async launchRequest(req: LspRequest) { - this.result.startTime = Date.now(); - let OK: number = 0; - let KO: number = 0; - const responseTimes = []; - for (let i = 0; i < this.times; i++) { - try { - const start = Date.now(); - await this.proxy!.handleRequest(req); - responseTimes.push(Date.now() - start); - OK += 1; - } catch (e) { - KO += 1; - } - } - this.result.KO = KO; - this.result.OK = OK; - this.result.KORate = ((KO / this.times) * 100).toFixed(2) + '%'; - this.result.rps = this.times / (Date.now() - this.result.startTime); - this.collectMetrics(responseTimes); - } - - private collectMetrics(responseTimes: number[]) { - this.result.latency_max = Math.max.apply(null, responseTimes); - this.result.latency_min = Math.min.apply(null, responseTimes); - this.result.latency_avg = sl.mean(responseTimes); - this.result.latency_medium = sl.median(responseTimes); - this.result.latency_95 = sl.percentile(responseTimes, 0.95); - this.result.latency_99 = sl.percentile(responseTimes, 0.99); - this.result.latency_std_dev = sl.stdev(responseTimes).toFixed(2); - } - - public dumpToCSV(resultFile: string) { - const newResult = _.mapKeys(this.result as _.Dictionary, (v, k) => { - if (k !== 'repoName') { - return `${requestTypeMapping.get(this.requestType)}_${k}`; - } else { - return 'repoName'; - } - }); - if (!fs.existsSync(resultFile)) { - console.log(papa.unparse([newResult])); - fs.writeFileSync(resultFile, papa.unparse([newResult])); - } else { - const file = fs.createReadStream(resultFile); - papa.parse(file, { - header: true, - complete: parsedResult => { - const originResults = parsedResult.data; - const index = originResults.findIndex(originResult => { - return originResult.repoName === newResult.repoName; - }); - if (index === -1) { - originResults.push(newResult); - } else { - originResults[index] = { ...originResults[index], ...newResult }; - } - fs.writeFileSync(resultFile, papa.unparse(originResults)); - }, - }); - } - } - - private async getAllFile() { - const gitOperator: GitOperations = new GitOperations(this.repo.path); - try { - const result: string[] = []; - const fileIterator = await gitOperator.iterateRepo('', 'HEAD'); - for await (const file of fileIterator) { - const filePath = file.path!; - if (filePath.endsWith(this.repo.language)) { - result.push(filePath); - } - } - return result; - } catch (e) { - console.error(`get files error: ${e}`); - throw e; - } - } - - private randomizePosition() { - // TODO:pcxu randomize position according to source file - return { - line: 19, - character: 2, - }; - } - - public async launchLspByLanguage() { - switch (this.repo.language) { - case 'java': { - this.proxy = await this.launchJavaLanguageServer(); - break; - } - case 'ts': { - this.proxy = await this.launchTypescriptLanguageServer(); - break; - } - default: { - console.error('unknown language type'); - break; - } - } - } - - private async launchTypescriptLanguageServer() { - const launcher = new TypescriptServerLauncher( - '127.0.0.1', - serverOptions, - new ConsoleLoggerFactory(), - TYPESCRIPT.embedPath! - ); - return await launcher.launch(false, 1); - } - - private async launchJavaLanguageServer() { - // @ts-ignore - const installManager = new InstallManager(null, serverOptions); - const launcher = new JavaLauncher( - '127.0.0.1', - serverOptions, - new ConsoleLoggerFactory(), - installManager.installationPath(JAVA) - ); - return await launcher.launch(false, 1); - } -} diff --git a/x-pack/legacy/plugins/code/server/lsp/mock_lang_server.js b/x-pack/legacy/plugins/code/server/lsp/mock_lang_server.js deleted file mode 100644 index 656f2face70b0..0000000000000 --- a/x-pack/legacy/plugins/code/server/lsp/mock_lang_server.js +++ /dev/null @@ -1,126 +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. - */ -/* eslint-disable */ -// This file is used in the test, using subprocess.fork to load a module directly, so this module can not be typescript -const net = require('net'); -const jsonrpc = require('vscode-jsonrpc'); -const createMessageConnection = jsonrpc.createMessageConnection; -const SocketMessageReader = jsonrpc.SocketMessageReader; -const SocketMessageWriter = jsonrpc.SocketMessageWriter; - -function log(msg) { - if (process.send) { - process.send(msg); - } - else { - // eslint-disable-next-line no-console - console.log(msg); - } -} -class MockLangServer { - constructor(host, port) { - this.host = host; - this.port = port; - this.socket = null; - this.connection = null; - this.shutdown = false; - } - /** - * connect remote server as a client - */ - connect() { - this.socket = new net.Socket(); - this.socket.on('connect', () => { - const reader = new SocketMessageReader(this.socket); - const writer = new SocketMessageWriter(this.socket); - this.connection = createMessageConnection(reader, writer); - this.connection.listen(); - this.connection.onNotification(this.onNotification.bind(this)); - this.connection.onRequest(this.onRequest.bind(this)); - log('socket connected'); - }); - this.socket.on('close', () => this.onSocketClosed()); - log('start connecting'); - this.socket.connect(this.port, this.host); - } - listen() { - const server = net.createServer(socket => { - server.close(); - socket.on('close', () => this.onSocketClosed()); - const reader = new SocketMessageReader(socket); - const writer = new SocketMessageWriter(socket); - this.connection = createMessageConnection(reader, writer); - this.connection.onNotification(this.onNotification.bind(this)); - this.connection.onRequest(this.onRequest.bind(this)); - this.connection.listen(); - log('socket connected'); - }); - server.on('error', err => { - log(err); - }); - log('start listening'); - server.listen(this.port); - } - onNotification(method, ...params) { - log({ method, params }); - // notify parent process what happened - if (method === 'exit') { - // https://microsoft.github.io/language-server-protocol/specification#exit - if (options.noExit) { - log('noExit'); - } - else { - const code = this.shutdown ? 0 : 1; - log(`exit process with code ${code}`); - process.exit(code); - } - } - } - onRequest(method, ...params) { - // notify parent process what requested - log({ method, params }); - if (method === 'shutdown') { - this.shutdown = true; - } - return { result: 'ok' }; - } - onSocketClosed() { - // notify parent process that socket closed - log('socket closed'); - } -} -log('process started'); -let port = 9999; -let host = ' localhost'; -const options = { noExit: false }; -let langServer; -process.on('message', (msg) => { - const [cmd, value] = msg.split(' '); - switch (cmd) { - case 'port': - port = parseInt(value, 10); - break; - case 'host': - host = value; - break; - case 'noExit': - options.noExit = true; - break; - case 'listen': - langServer = new MockLangServer(host, port); - langServer.listen(); - break; - case 'connect': - langServer = new MockLangServer(host, port); - langServer.connect(); - break; - case 'quit': - process.exit(0); - break; - default: - // nothing to do - } -}); diff --git a/x-pack/legacy/plugins/code/server/lsp/process/embed_ctag_server.ts b/x-pack/legacy/plugins/code/server/lsp/process/embed_ctag_server.ts deleted file mode 100644 index 2967026f397bc..0000000000000 --- a/x-pack/legacy/plugins/code/server/lsp/process/embed_ctag_server.ts +++ /dev/null @@ -1,33 +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 * as lsp from 'vscode-languageserver'; -import { IConnection } from 'vscode-languageserver'; -import { createLspConnection } from '@elastic/ctags-langserver/lib/lsp-connection'; -import { Logger } from '../../log'; -import { EmbedProgram } from './embed_program'; - -export class EmbedCtagServer extends EmbedProgram { - private connection: IConnection | null = null; - constructor(public readonly port: number, log: Logger) { - super(log); - } - - async stop(): Promise { - if (this.connection) { - this.connection.dispose(); - } - } - - async start(): Promise { - this.connection = createLspConnection({ - ctagsPath: '', - showMessageLevel: lsp.MessageType.Warning, - socketPort: this.port, - }); - this.connection.listen(); - } -} diff --git a/x-pack/legacy/plugins/code/server/lsp/process/embed_program.ts b/x-pack/legacy/plugins/code/server/lsp/process/embed_program.ts deleted file mode 100644 index a82a954851712..0000000000000 --- a/x-pack/legacy/plugins/code/server/lsp/process/embed_program.ts +++ /dev/null @@ -1,40 +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 { EventEmitter } from 'events'; -import { ControlledProgram } from './controlled_program'; -import { Logger } from '../../log'; - -let globalPid = 0; - -export abstract class EmbedProgram implements ControlledProgram { - private eventEmitter = new EventEmitter(); - private _killed: boolean = false; - protected constructor(public readonly log: Logger) { - this.pid = globalPid++; - } - - readonly pid: number; - - kill(force?: boolean): void { - this.stop().then(() => { - this.log.debug('embed program stopped'); - this._killed = true; - this.eventEmitter.emit('exit'); - }); - } - - killed(): boolean { - return this._killed; - } - - onExit(callback: () => void) { - this.eventEmitter.on('exit', callback); - } - - abstract async stop(): Promise; - abstract async start(): Promise; -} diff --git a/x-pack/legacy/plugins/code/server/lsp/process/external_program.ts b/x-pack/legacy/plugins/code/server/lsp/process/external_program.ts deleted file mode 100644 index 21100149e498e..0000000000000 --- a/x-pack/legacy/plugins/code/server/lsp/process/external_program.ts +++ /dev/null @@ -1,66 +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 fs from 'fs'; -import { ChildProcess } from 'child_process'; -import { ControlledProgram } from './controlled_program'; -import { ServerOptions } from '../../server_options'; -import { Logger } from '../../log'; - -const OOM_SCORE_ADJ = 667; -const OOM_ADJ = 10; - -export class ExternalProgram implements ControlledProgram { - constructor( - public readonly child: ChildProcess, - public readonly options: ServerOptions, - public readonly log: Logger - ) { - this.pid = child.pid; - if (this.options.lsp.oomScoreAdj && process.platform === 'linux') { - this.adjustOom(this.pid); - } - this.log.debug('spawned a child process ' + this.pid); - } - - kill(force: boolean = false): void { - if (force) { - this.child.kill('SIGKILL'); - this.log.info('force killed process ' + this.pid); - } else { - this.child.kill(); - this.log.info('killed process ' + this.pid); - } - } - - killed(): boolean { - return this.child.killed; - } - - onExit(callback: () => void) { - this.child.on('exit', callback); - } - - readonly pid: number; - - private adjustOom(pid: number) { - try { - fs.writeFileSync(`/proc/${pid}/oom_score_adj`, `${OOM_SCORE_ADJ}\n`); - this.log.debug(`wrote oom_score_adj of process ${pid} to ${OOM_SCORE_ADJ}`); - } catch (e) { - this.log.warn(e); - try { - fs.writeFileSync(`/proc/${pid}/oom_adj`, `${OOM_ADJ}\n`); - this.log.debug(`wrote oom_adj of process ${pid} to ${OOM_ADJ}`); - } catch (err) { - this.log.warn( - 'write oom_score_adj and oom_adj file both failed, reduce priority not working' - ); - this.log.warn(err); - } - } - } -} diff --git a/x-pack/legacy/plugins/code/server/lsp/proxy.ts b/x-pack/legacy/plugins/code/server/lsp/proxy.ts deleted file mode 100644 index 3ba796df9e733..0000000000000 --- a/x-pack/legacy/plugins/code/server/lsp/proxy.ts +++ /dev/null @@ -1,285 +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 EventEmitter from 'events'; -import * as net from 'net'; -import { fileURLToPath } from 'url'; -import { - createMessageConnection, - MessageConnection, - SocketMessageReader, - SocketMessageWriter, -} from 'vscode-jsonrpc'; -import { ResponseError, ResponseMessage } from 'vscode-jsonrpc/lib/messages'; - -import { - ClientCapabilities, - ExitNotification, - InitializedNotification, - InitializeResult, - LogMessageNotification, - MessageType, - WorkspaceFolder, -} from 'vscode-languageserver-protocol/lib/main'; - -import { LspRequest } from '../../model'; -import { Logger } from '../log'; -import { InternalError, RequestCancelled } from '../../common/lsp_error_codes'; -import { InitializeOptions, WorkspaceStatus } from './request_expander'; - -export interface ILanguageServerHandler { - lastAccess?: number; - handleRequest(request: LspRequest): Promise; - exit(): Promise; - unloadWorkspace(workspaceDir: string): Promise; - initializeState?( - workspaceDir: string - ): Promise<{ [lang: string]: WorkspaceStatus }> | WorkspaceStatus; -} - -export class LanguageServerProxy implements ILanguageServerHandler { - private error: any | null = null; - public get isClosed() { - return this.closed; - } - - public initialized: boolean = false; - private socket: any; - private clientConnection: MessageConnection | null = null; - private closed: boolean = false; - private readonly targetHost: string; - private targetPort: number; - private readonly logger: Logger; - private eventEmitter = new EventEmitter(); - private currentServer: net.Server | null = null; - private listeningPort: number | null = null; - - constructor(targetPort: number, targetHost: string, logger: Logger) { - this.targetHost = targetHost; - this.targetPort = targetPort; - this.logger = logger; - } - - public async handleRequest(request: LspRequest): Promise { - const response: ResponseMessage = { - jsonrpc: '', - id: null, - }; - - const conn = await this.connected(); - const params = Array.isArray(request.params) ? request.params : [request.params]; - if (!request.isNotification) { - const file = request.documentUri || request.workspacePath; - this.logger.debug(`sending request ${request.method} for ${file}`); - response.result = await conn.sendRequest(request.method, ...params); - this.logger.debug(`request ${request.method} for ${file} done.`); - } else { - conn.sendNotification(request.method, ...params); - } - return response; - } - - public connected(): Promise { - if (this.closed) { - return Promise.reject(new ResponseError(RequestCancelled, 'Server closed')); - } else if (this.error) { - return Promise.reject(new ResponseError(InternalError, 'Server error', this.error)); - } else if (this.clientConnection) { - return Promise.resolve(this.clientConnection); - } else { - return new Promise((resolve, reject) => { - this.eventEmitter.on('err', error => - reject(new ResponseError(InternalError, 'Server error', error)) - ); - this.eventEmitter.on('exit', () => { - reject(new ResponseError(RequestCancelled, 'Server closed')); - this.initialized = false; - }); - this.eventEmitter.on('connect', () => resolve(this.clientConnection!)); - }); - } - } - - public async initialize( - clientCapabilities: ClientCapabilities, - workspaceFolders: [WorkspaceFolder], - initOptions?: InitializeOptions - ): Promise { - if (this.error) { - throw this.error; - } - const clientConn = await this.connected(); - const rootUri = workspaceFolders[0].uri; - if ( - initOptions && - initOptions.clientCapabilities && - Object.keys(clientCapabilities).length === 0 - ) { - clientCapabilities = initOptions.clientCapabilities; - } - const params = { - processId: null, - workspaceFolders, - rootUri, - capabilities: clientCapabilities, - rootPath: fileURLToPath(rootUri), - }; - this.logger.debug(`sending initialize for ${params.rootPath}`); - return await clientConn - .sendRequest( - 'initialize', - initOptions && initOptions.initialOptions - ? { ...params, initializationOptions: initOptions.initialOptions } - : params - ) - .then(r => { - this.logger.info(`initialized at ${rootUri}`); - - // @ts-ignore - // TODO fix this - clientConn.sendNotification(InitializedNotification.type, {}); - this.initialized = true; - return r as InitializeResult; - }); - } - - public async shutdown() { - const clientConn = await this.connected(); - this.logger.info(`sending shutdown request`); - return await clientConn.sendRequest('shutdown'); - } - /** - * send a exit request to Language Server - * https://microsoft.github.io/language-server-protocol/specification#exit - */ - public async exit() { - this.closed = true; // stop the socket reconnect - if (this.clientConnection) { - this.logger.info('sending `shutdown` request to language server.'); - const clientConn = this.clientConnection; - clientConn.sendRequest('shutdown'); - this.logger.info('sending `exit` notification to language server.'); - // @ts-ignore - clientConn.sendNotification(ExitNotification.type); - } - this.eventEmitter.emit('exit'); - this.initialized = false; - } - - public startServerConnection() { - // prevent calling this method multiple times which may cause 'port already in use' error - if (this.currentServer) { - if (this.listeningPort === this.targetPort) { - return; - } else { - this.currentServer!.close(); - } - } - const server = net.createServer(socket => { - this.initialized = false; - server.close(); - this.currentServer = null; - socket.on('close', () => this.onSocketClosed()); - this.eventEmitter.off('changePort', server.close); - this.logger.info('langserver connection established on port ' + this.targetPort); - const reader = new SocketMessageReader(socket); - const writer = new SocketMessageWriter(socket); - this.clientConnection = createMessageConnection(reader, writer, this.logger); - this.registerOnNotificationHandler(this.clientConnection); - this.clientConnection.listen(); - this.eventEmitter.emit('connect'); - }); - server.on('error', this.setError); - const port = this.targetPort; - server.listen(port, () => { - this.listeningPort = port; - this.currentServer = server; - server.removeListener('error', this.setError); - this.logger.info('Wait langserver connection on port ' + this.targetPort); - }); - } - - /** - * get notification when proxy's socket disconnect - * @param listener - */ - public onDisconnected(listener: () => void) { - this.eventEmitter.on('close', listener); - } - - public onExit(listener: () => void) { - this.eventEmitter.on('exit', listener); - } - - /** - * get notification when proxy's socket connect - * @param listener - */ - public onConnected(listener: () => void) { - this.eventEmitter.on('connect', listener); - } - - public connect() { - this.logger.debug('connecting'); - this.socket = new net.Socket(); - - this.socket.on('connect', () => { - const reader = new SocketMessageReader(this.socket); - const writer = new SocketMessageWriter(this.socket); - this.clientConnection = createMessageConnection(reader, writer, this.logger); - this.registerOnNotificationHandler(this.clientConnection); - this.clientConnection.listen(); - this.eventEmitter.emit('connect'); - }); - - this.socket.on('close', () => this.onSocketClosed()); - - this.socket.on('error', () => void 0); - this.socket.on('timeout', () => void 0); - this.socket.on('drain', () => void 0); - this.socket.connect(this.targetPort, this.targetHost); - } - - public unloadWorkspace(workspaceDir: string): Promise { - return Promise.reject('should not hit here'); - } - - private onSocketClosed() { - this.clientConnection = null; - this.initialized = false; - this.eventEmitter.emit('close'); - } - - private registerOnNotificationHandler(clientConnection: MessageConnection) { - // @ts-ignore - clientConnection.onNotification(LogMessageNotification.type, notification => { - switch (notification.type) { - case MessageType.Log: - this.logger.debug(notification.message); - break; - case MessageType.Info: - this.logger.debug(notification.message); - break; - case MessageType.Warning: - this.logger.warn(notification.message); - break; - case MessageType.Error: - this.logger.error(notification.message); - break; - } - }); - } - - public changePort(port: number) { - if (port !== this.targetPort) { - this.targetPort = port; - } - } - - public setError(error: any) { - this.error = error; - this.eventEmitter.emit('err', error); - } -} diff --git a/x-pack/legacy/plugins/code/server/lsp/replies_map.ts b/x-pack/legacy/plugins/code/server/lsp/replies_map.ts deleted file mode 100644 index 12ed53b9b5738..0000000000000 --- a/x-pack/legacy/plugins/code/server/lsp/replies_map.ts +++ /dev/null @@ -1,16 +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 { ResponseMessage } from 'vscode-jsonrpc/lib/messages'; - -export type RepliesMap = Map< - number, - [(resp: ResponseMessage) => void, (error: ResponseMessage['error']) => void] ->; - -export function createRepliesMap() { - return new Map() as RepliesMap; -} diff --git a/x-pack/legacy/plugins/code/server/lsp/request_expander.test.ts b/x-pack/legacy/plugins/code/server/lsp/request_expander.test.ts deleted file mode 100644 index cf3999ec244f5..0000000000000 --- a/x-pack/legacy/plugins/code/server/lsp/request_expander.test.ts +++ /dev/null @@ -1,179 +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 fs from 'fs'; -import rimraf from 'rimraf'; -import sinon from 'sinon'; -import { pathToFileURL } from 'url'; - -import { ServerOptions } from '../server_options'; -import { LanguageServerProxy } from './proxy'; -import { RequestExpander, WorkspaceUnloadedError } from './request_expander'; -import { ConsoleLogger } from '../utils/console_logger'; - -// @ts-ignore -const options: ServerOptions = { - workspacePath: '/tmp/test/workspace', -}; -beforeEach(async () => { - sinon.reset(); - if (!fs.existsSync(options.workspacePath)) { - fs.mkdirSync(options.workspacePath, { recursive: true }); - } -}); - -afterEach(() => { - return new Promise(resolve => { - rimraf(options.workspacePath, resolve); - }); -}); - -function createMockProxy(initDelay: number = 0, requestDelay: number = 0) { - // @ts-ignore - const proxyStub = sinon.createStubInstance(LanguageServerProxy, { - handleRequest: sinon.stub().callsFake(() => { - if (requestDelay > 0) { - const start = Date.now(); - return new Promise(resolve => { - setTimeout(() => { - resolve({ result: { start, end: Date.now() } }); - }, requestDelay); - }); - } else { - return sinon.stub().resolvesArg(0); - } - }), - initialize: sinon.stub().callsFake( - () => - new Promise(resolve => { - proxyStub.initialized = true; - if (initDelay > 0) { - setTimeout(() => { - resolve(); - }, initDelay); - } else { - resolve(); - } - }) - ), - }); - return proxyStub; -} - -const log = new ConsoleLogger(); - -test('be able to open multiple workspace', async () => { - const proxyStub = createMockProxy(); - const expander = new RequestExpander(proxyStub, true, 2, options, {}, log); - const request1 = { - method: 'request1', - params: [], - workspacePath: '/tmp/test/workspace/1', - }; - - const request2 = { - method: 'request2', - params: [], - workspacePath: '/tmp/test/workspace/2', - }; - fs.mkdirSync(request1.workspacePath, { recursive: true }); - fs.mkdirSync(request2.workspacePath, { recursive: true }); - await expander.handleRequest(request1); - await expander.handleRequest(request2); - expect(proxyStub.initialize.called); - expect( - proxyStub.initialize.calledOnceWith({}, [ - { - name: request1.workspacePath, - uri: pathToFileURL(request1.workspacePath).href, - }, - ]) - ).toBeTruthy(); - expect( - proxyStub.handleRequest.calledWith({ - method: 'workspace/didChangeWorkspaceFolders', - params: { - event: { - added: [ - { - name: request2.workspacePath, - uri: pathToFileURL(request2.workspacePath).href, - }, - ], - removed: [], - }, - }, - isNotification: true, - }) - ).toBeTruthy(); -}); - -test('be able to swap workspace', async () => { - const proxyStub = createMockProxy(); - const expander = new RequestExpander(proxyStub, true, 1, options, {}, log); - const request1 = { - method: 'request1', - params: [], - workspacePath: '/tmp/test/workspace/1', - }; - const request2 = { - method: 'request2', - params: [], - workspacePath: '/tmp/test/workspace/2', - }; - fs.mkdirSync(request1.workspacePath, { recursive: true }); - fs.mkdirSync(request2.workspacePath, { recursive: true }); - await expander.handleRequest(request1); - await expander.handleRequest(request2); - expect(proxyStub.initialize.called); - expect( - proxyStub.handleRequest.calledWith({ - method: 'workspace/didChangeWorkspaceFolders', - params: { - event: { - added: [ - { - name: request2.workspacePath, - uri: pathToFileURL(request2.workspacePath).href, - }, - ], - removed: [ - { - name: request1.workspacePath, - uri: pathToFileURL(request1.workspacePath).href, - }, - ], - }, - }, - isNotification: true, - }) - ).toBeTruthy(); -}); - -test('requests should be cancelled if workspace is unloaded', async () => { - // @ts-ignore - - const clock = sinon.useFakeTimers(); - const proxyStub = createMockProxy(300); - const expander = new RequestExpander(proxyStub, true, 1, options, {}, log); - const workspace1 = '/tmp/test/workspace/1'; - const request = { - method: 'request1', - params: [], - workspacePath: workspace1, - }; - fs.mkdirSync(workspace1, { recursive: true }); - const promise1 = expander.handleRequest(request); - const promise2 = expander.handleRequest(request); - setTimeout(() => expander.unloadWorkspace(workspace1), 1); - clock.tick(100); - - process.nextTick(() => clock.runAll()); - await expect(promise1).rejects.toEqual(WorkspaceUnloadedError); - await expect(promise2).rejects.toEqual(WorkspaceUnloadedError); - - clock.restore(); -}); diff --git a/x-pack/legacy/plugins/code/server/lsp/request_expander.ts b/x-pack/legacy/plugins/code/server/lsp/request_expander.ts deleted file mode 100644 index 35d4d4b384664..0000000000000 --- a/x-pack/legacy/plugins/code/server/lsp/request_expander.ts +++ /dev/null @@ -1,272 +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 fs from 'fs'; -import path from 'path'; -import { pathToFileURL } from 'url'; -import { ResponseError, ResponseMessage } from 'vscode-jsonrpc/lib/messages'; -import { - ClientCapabilities, - DidChangeWorkspaceFoldersParams, - InitializeResult, -} from 'vscode-languageserver-protocol'; -import { RequestCancelled } from '../../common/lsp_error_codes'; -import { LspRequest } from '../../model'; -import { Logger } from '../log'; -import { ServerOptions } from '../server_options'; -import { Cancelable } from '../utils/cancelable'; -import { ILanguageServerHandler, LanguageServerProxy } from './proxy'; - -export enum WorkspaceStatus { - Uninitialized = 'Uninitialized', - Initializing = 'Initializing', - Initialized = 'Initialized', -} - -interface Workspace { - lastAccess: number; - status: WorkspaceStatus; - initPromise?: Cancelable; -} - -export interface InitializeOptions { - clientCapabilities?: ClientCapabilities; - initialOptions?: object; -} - -export const WorkspaceUnloadedError = new ResponseError(RequestCancelled, 'Workspace unloaded'); - -export class RequestExpander implements ILanguageServerHandler { - public lastAccess: number = 0; - private proxy: LanguageServerProxy; - // a map for workspacePath -> Workspace - private workspaces: Map = new Map(); - private readonly workspaceRoot: string; - private exited = false; - - constructor( - proxy: LanguageServerProxy, - public readonly builtinWorkspace: boolean, - public readonly maxWorkspace: number, - public readonly serverOptions: ServerOptions, - public readonly initialOptions: InitializeOptions, - public readonly log: Logger - ) { - this.proxy = proxy; - const clearListener = () => { - this.log.debug('proxy disconnected, clearing workspace status'); - this.workspaces.clear(); - }; - proxy.onDisconnected(clearListener); - proxy.onExit(clearListener); - this.workspaceRoot = fs.realpathSync(this.serverOptions.workspacePath); - } - - public handleRequest(request: LspRequest): Promise { - this.lastAccess = Date.now(); - return new Promise((resolve, reject) => { - if (this.exited) { - reject(new Error('proxy is exited.')); - return; - } - this.log.debug(`handling a ${request.method} job for workspace ${request.workspacePath}`); - this.expand(request).then( - value => { - resolve(value); - }, - err => { - this.log.error(err); - reject(err); - } - ); - }); - } - - public async exit() { - this.exited = true; - this.log.debug(`exiting proxy`); - return this.proxy.exit(); - } - - public async unloadWorkspace(workspacePath: string) { - this.log.debug('unload workspace ' + workspacePath); - if (this.hasWorkspacePath(workspacePath)) { - const ws = this.getWorkspace(workspacePath); - if (ws.initPromise) { - ws.initPromise.cancel(WorkspaceUnloadedError); - } - if (this.builtinWorkspace) { - const params: DidChangeWorkspaceFoldersParams = { - event: { - removed: [ - { - name: workspacePath!, - uri: pathToFileURL(workspacePath).href, - }, - ], - added: [], - }, - }; - await this.proxy.handleRequest({ - method: 'workspace/didChangeWorkspaceFolders', - params, - isNotification: true, - }); - } else { - await this.exit(); - } - } - this.removeWorkspace(workspacePath); - } - - public async initialize(workspacePath: string): Promise { - this.updateWorkspace(workspacePath); - const ws = this.getWorkspace(workspacePath); - ws.status = WorkspaceStatus.Initializing; - - try { - if (this.builtinWorkspace) { - if (this.proxy.initialized) { - await this.changeWorkspaceFolders(workspacePath, this.maxWorkspace); - } else { - // this is the first workspace, init the lsp server first - await this.sendInitRequest(workspacePath); - } - ws.status = WorkspaceStatus.Initialized; - delete ws.initPromise; - } else { - for (const w of this.workspaces.values()) { - if (w.status === WorkspaceStatus.Initialized) { - await this.proxy.shutdown(); - this.workspaces.clear(); - break; - } - } - const response = await this.sendInitRequest(workspacePath); - ws.status = WorkspaceStatus.Initialized; - return response; - } - } catch (e) { - ws.status = WorkspaceStatus.Uninitialized; - throw e; - } - } - - private async sendInitRequest(workspacePath: string) { - return await this.proxy.initialize( - {}, - [ - { - name: workspacePath, - uri: pathToFileURL(workspacePath).href, - }, - ], - this.initialOptions - ); - } - - private async expand(request: LspRequest): Promise { - if (request.workspacePath) { - const ws = this.getWorkspace(request.workspacePath); - if (ws.status === WorkspaceStatus.Uninitialized) { - ws.initPromise = Cancelable.fromPromise(this.initialize(request.workspacePath)); - } - // Uninitialized or initializing - if (ws.status === WorkspaceStatus.Initializing) { - await ws.initPromise!.promise; - } - } - return await this.proxy.handleRequest(request); - } - - /** - * use DidChangeWorkspaceFolders notification add a new workspace folder - * replace the oldest one if count > maxWorkspace - * builtinWorkspace = false is equal to maxWorkspace =1 - * @param workspacePath - * @param maxWorkspace - */ - private async changeWorkspaceFolders(workspacePath: string, maxWorkspace: number): Promise { - const params: DidChangeWorkspaceFoldersParams = { - event: { - added: [ - { - name: workspacePath!, - uri: pathToFileURL(workspacePath).href, - }, - ], - removed: [], - }, - }; - this.updateWorkspace(workspacePath); - - if (this.workspaces.size > this.maxWorkspace) { - let oldestWorkspace; - let oldestAccess = Number.MAX_VALUE; - for (const [workspace, ws] of this.workspaces) { - if (ws.lastAccess < oldestAccess) { - oldestAccess = ws.lastAccess; - oldestWorkspace = path.join(this.serverOptions.workspacePath, workspace); - } - } - if (oldestWorkspace) { - params.event.removed.push({ - name: oldestWorkspace, - uri: pathToFileURL(oldestWorkspace).href, - }); - this.removeWorkspace(oldestWorkspace); - } - } - // adding a workspace folder may also need initialize - await this.proxy.handleRequest({ - method: 'workspace/didChangeWorkspaceFolders', - params, - isNotification: true, - }); - } - - private removeWorkspace(workspacePath: string) { - this.workspaces.delete(this.relativePath(workspacePath)); - } - - private updateWorkspace(workspacePath: string) { - this.getWorkspace(workspacePath).lastAccess = Date.now(); - } - - private hasWorkspacePath(workspacePath: string) { - return this.workspaces.has(this.relativePath(workspacePath)); - } - - /** - * use a relative path to prevent bugs due to symbolic path - * @param workspacePath - */ - private relativePath(workspacePath: string) { - const realPath = fs.realpathSync(workspacePath); - return path.relative(this.workspaceRoot, realPath); - } - - private getWorkspace(workspacePath: string): Workspace { - const p = this.relativePath(workspacePath); - let ws = this.workspaces.get(p); - if (!ws) { - ws = { - status: WorkspaceStatus.Uninitialized, - lastAccess: Date.now(), - }; - this.workspaces.set(p, ws); - } - return ws; - } - - initializeState(workspaceDir: string): WorkspaceStatus { - const ws = this.getWorkspace(workspaceDir); - if (ws.status === WorkspaceStatus.Uninitialized) { - ws.initPromise = Cancelable.fromPromise(this.initialize(workspaceDir)); - } - return ws.status; - } -} diff --git a/x-pack/legacy/plugins/code/server/lsp/test_config.yml b/x-pack/legacy/plugins/code/server/lsp/test_config.yml deleted file mode 100644 index f359261f80247..0000000000000 --- a/x-pack/legacy/plugins/code/server/lsp/test_config.yml +++ /dev/null @@ -1,7 +0,0 @@ -repos: - - url: 'https://github.com/javaee-samples/javaee7-simple-sample.git' - path: '/tmp/test/code/repos/github.com/javaee-samples/javaee7-simple-sample' - language: 'java' - - url: 'https://github.com/Microsoft/TypeScript-Node-Starter.git' - path: '/tmp/test/code/repos/github.com/Microsoft/TypeScript-Node-Starter' - language: 'ts' diff --git a/x-pack/legacy/plugins/code/server/lsp/test_repo_manager.ts b/x-pack/legacy/plugins/code/server/lsp/test_repo_manager.ts deleted file mode 100644 index 8c0594a0f1851..0000000000000 --- a/x-pack/legacy/plugins/code/server/lsp/test_repo_manager.ts +++ /dev/null @@ -1,48 +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. - */ -/* eslint-disable no-console */ - -import fs from 'fs'; -import rimraf from 'rimraf'; - -import { TestConfig, Repo } from '../../model/test_config'; -import { prepareProjectByCloning } from '../test_utils'; - -export class TestRepoManager { - private repos: Repo[]; - - constructor(testConfig: TestConfig) { - this.repos = testConfig.repos; - } - - public async importAllRepos() { - for (const repo of this.repos) { - await prepareProjectByCloning(repo.url, repo.path); - } - } - - public async cleanAllRepos() { - this.repos.forEach(repo => { - this.cleanRepo(repo.path); - }); - } - - public async cleanRepo(path: string) { - return new Promise(resolve => { - if (fs.existsSync(path)) { - rimraf(path, resolve); - } else { - resolve(true); - } - }); - } - - public getRepo(language: string): Repo { - return this.repos.filter(repo => { - return repo.language === language; - })[0]; - } -} diff --git a/x-pack/legacy/plugins/code/server/lsp/ts_launcher.ts b/x-pack/legacy/plugins/code/server/lsp/ts_launcher.ts deleted file mode 100644 index a2474e18243eb..0000000000000 --- a/x-pack/legacy/plugins/code/server/lsp/ts_launcher.ts +++ /dev/null @@ -1,71 +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 { spawn } from 'child_process'; -import getPort from 'get-port'; -import { Logger } from '../log'; -import { ServerOptions } from '../server_options'; -import { LoggerFactory } from '../utils/log_factory'; -import { AbstractLauncher } from './abstract_launcher'; -import { LanguageServerProxy } from './proxy'; -import { InitializeOptions, RequestExpander } from './request_expander'; -import { ExternalProgram } from './process/external_program'; -import { ControlledProgram } from './process/controlled_program'; - -const TS_LANG_DETACH_PORT = 2089; - -export class TypescriptServerLauncher extends AbstractLauncher { - constructor( - public readonly targetHost: string, - public readonly options: ServerOptions, - public readonly loggerFactory: LoggerFactory, - public readonly installationPath: string - ) { - super('typescript', targetHost, options, loggerFactory); - } - - async getPort() { - if (!this.options.lsp.detach) { - return await getPort(); - } - return TS_LANG_DETACH_PORT; - } - - protected startupTimeout = 5000; - - createExpander( - proxy: LanguageServerProxy, - builtinWorkspace: boolean, - maxWorkspace: number - ): RequestExpander { - return new RequestExpander( - proxy, - builtinWorkspace, - maxWorkspace, - this.options, - { - initialOptions: { - installNodeDependency: this.options.security.installNodeDependency, - gitHostWhitelist: this.options.security.gitHostWhitelist, - }, - } as InitializeOptions, - this.log - ); - } - async spawnProcess(port: number, log: Logger): Promise { - const p = spawn(process.execPath, [this.installationPath, '-p', port.toString(), '-c', '1'], { - detached: false, - stdio: 'pipe', - }); - p.stdout.on('data', data => { - log.stdout(data.toString()); - }); - p.stderr.on('data', data => { - log.stderr(data.toString()); - }); - return new ExternalProgram(p, this.options, log); - } -} diff --git a/x-pack/legacy/plugins/code/server/lsp/workspace_command.test.ts b/x-pack/legacy/plugins/code/server/lsp/workspace_command.test.ts deleted file mode 100644 index f83fbcef13a22..0000000000000 --- a/x-pack/legacy/plugins/code/server/lsp/workspace_command.test.ts +++ /dev/null @@ -1,74 +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 os from 'os'; -import { spawn } from 'child_process'; -import { Logger } from '../log'; -import { WorkspaceCommand } from './workspace_command'; -import { RepoConfig } from '../../model/workspace'; -jest.mock('child_process', () => ({ - spawn: jest.fn().mockImplementation(() => ({ - stdout: { on: jest.fn() }, - stderr: { on: jest.fn() }, - on: jest.fn().mockImplementation((event, cb) => { - cb('foo-code', 'foo-signal'); - }), - })), -})); -jest.mock('proper-lockfile', () => ({ - check: jest.fn().mockReturnValue(false), - lock: jest.fn().mockImplementation(() => { - return () => {}; - }), -})); - -afterEach(() => { - jest.clearAllMocks(); -}); - -it(`spawns process if repoConfig.init comes from own properties`, async () => { - const repoConfig = { - repo: 'https://github.com/elastic/foo', - init: ['echo', 'hello'], - }; - const mockLogger = { - info: jest.fn(), - error: jest.fn(), - }; - const command = new WorkspaceCommand( - repoConfig, - os.tmpdir(), - 'foo-revision', - (mockLogger as unknown) as Logger - ); - await command.runInit(true); - expect(spawn).toHaveBeenCalled(); -}); - -it(`doesn't spawn process if repoConfig.init comes from prototypes properties`, async () => { - const prototype = { - init: ['echo', 'noooo'], - }; - - const repoConfig = { - repo: 'https://github.com/elastic/foo', - }; - - Object.setPrototypeOf(repoConfig, prototype); - - const mockLogger = { - info: jest.fn(), - error: jest.fn(), - }; - const command = new WorkspaceCommand( - (repoConfig as unknown) as RepoConfig, - os.tmpdir(), - 'foo-revision', - (mockLogger as unknown) as Logger - ); - await command.runInit(true); - expect(spawn).not.toHaveBeenCalled(); -}); diff --git a/x-pack/legacy/plugins/code/server/lsp/workspace_command.ts b/x-pack/legacy/plugins/code/server/lsp/workspace_command.ts deleted file mode 100644 index 10c2b97197710..0000000000000 --- a/x-pack/legacy/plugins/code/server/lsp/workspace_command.ts +++ /dev/null @@ -1,111 +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 { spawn } from 'child_process'; -import fs from 'fs'; -import path from 'path'; -import LockFile from 'proper-lockfile'; -import stream from 'stream'; -import { RepoCmd, RepoConfig } from '../../model/workspace'; -import { Logger } from '../log'; - -export class WorkspaceCommand { - constructor( - public readonly repoConfig: RepoConfig, - public readonly workspaceDir: string, - public readonly revision: string, - public readonly log: Logger - ) {} - - public async runInit(force: boolean) { - if (this.repoConfig.init) { - const versionFile = path.join(this.workspaceDir, 'init.version'); - if (this.checkRevision(versionFile) && !force) { - this.log.info('a same revision exists, init cmd skipped.'); - return; - } - const lockFile = this.workspaceDir; // path.join(this.workspaceDir, 'init.lock'); - - const isLocked = await LockFile.check(lockFile); - if (isLocked) { - this.log.info('another process is running, please try again later'); - return; - } - const release = await LockFile.lock(lockFile); - - try { - if (!this.repoConfig.hasOwnProperty('init')) { - throw new Error( - `RepoConfig's init comes from a prototype, this is unexpected and unsupported` - ); - } - const process = this.spawnProcess(this.repoConfig.init); - const logFile = path.join(this.workspaceDir, 'init.log'); - const logFileStream = fs.createWriteStream(logFile, { encoding: 'utf-8', flags: 'a+' }); - this.redirectOutput(process.stdout, logFileStream); - this.redirectOutput(process.stderr, logFileStream, true); - process.on('close', async (code, signal) => { - logFileStream.close(); - await this.writeRevisionFile(versionFile); - this.log.info(`init process finished with code: ${code} signal: ${signal}`); - await release(); - }); - } catch (e) { - this.log.error(e); - release(); - } - } - } - - private spawnProcess(repoCmd: RepoCmd) { - let cmd: string; - let args: string[]; - if (typeof repoCmd === 'string') { - cmd = repoCmd; - args = []; - } else { - [cmd, ...args] = repoCmd as string[]; - } - return spawn(cmd, args, { - detached: false, - cwd: this.workspaceDir, - }); - } - - private redirectOutput(from: stream.Readable, to: fs.WriteStream, isError: boolean = false) { - from.on('data', (data: Buffer) => { - if (isError) { - this.log.error(data.toString('utf-8')); - } else { - this.log.info(data.toString('utf-8')); - } - to.write(data); - }); - } - - /** - * check the revision file in workspace, return true if it exists and its content equals current revision. - * @param versionFile - */ - private checkRevision(versionFile: string) { - if (fs.existsSync(versionFile)) { - const revision = fs.readFileSync(versionFile, 'utf8').trim(); - return revision === this.revision; - } - return false; - } - - private writeRevisionFile(versionFile: string) { - return new Promise((resolve, reject) => { - fs.writeFile(versionFile, this.revision, err => { - if (err) { - reject(err); - } - resolve(); - }); - }); - } -} diff --git a/x-pack/legacy/plugins/code/server/lsp/workspace_handler.test.ts b/x-pack/legacy/plugins/code/server/lsp/workspace_handler.test.ts deleted file mode 100644 index d959eda2aa6dc..0000000000000 --- a/x-pack/legacy/plugins/code/server/lsp/workspace_handler.test.ts +++ /dev/null @@ -1,147 +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 fs from 'fs'; -import path from 'path'; - -import * as os from 'os'; -import rimraf from 'rimraf'; -import { ResponseMessage } from 'vscode-jsonrpc/lib/messages'; - -import { LspRequest } from '../../model'; -import { GitOperations } from '../git_operations'; -import { ConsoleLoggerFactory } from '../utils/console_logger_factory'; -import { WorkspaceHandler } from './workspace_handler'; - -const baseDir = fs.mkdtempSync(path.join(os.tmpdir(), 'code_test')); -const workspaceDir = path.join(baseDir, 'workspace'); -const repoDir = path.join(baseDir, 'repo'); -const gitOps = new GitOperations(repoDir); - -function handleResponseUri(wh: WorkspaceHandler, uri: string) { - const dummyRequest: LspRequest = { - method: 'textDocument/edefinition', - params: [], - }; - const dummyResponse: ResponseMessage = { - id: null, - jsonrpc: '', - result: [ - { - location: { - uri, - }, - }, - ], - }; - wh.handleResponse(dummyRequest, dummyResponse); - return dummyResponse.result[0].location.uri; -} - -function makeAFile( - workspacePath: string = workspaceDir, - repo = 'github.com/Microsoft/TypeScript-Node-Starter', - revision = 'master', - file = 'src/controllers/user.ts' -) { - const fullPath = path.join(workspacePath, repo, '__randomString', revision, file); - fs.mkdirSync(path.dirname(fullPath), { recursive: true }); - fs.writeFileSync(fullPath, ''); - const strInUrl = fullPath - .split(path.sep) - .map(value => encodeURIComponent(value)) - .join('/'); - const uri = `file:///${strInUrl}`; - return { repo, revision, file, uri }; -} - -test('file system url should be converted', async () => { - const workspaceHandler = new WorkspaceHandler( - gitOps, - workspaceDir, - // @ts-ignore - null, - new ConsoleLoggerFactory() - ); - const { repo, revision, file, uri } = makeAFile(workspaceDir); - const converted = handleResponseUri(workspaceHandler, uri); - expect(converted).toBe(`git://${repo}/blob/${revision}/${file}`); -}); - -test('should support symbol link', async () => { - const symlinkToWorkspace = path.join(baseDir, 'linkWorkspace'); - fs.symlinkSync(workspaceDir, symlinkToWorkspace, 'dir'); - // @ts-ignore - const workspaceHandler = new WorkspaceHandler( - gitOps, - symlinkToWorkspace, - // @ts-ignore - null, - new ConsoleLoggerFactory() - ); - - const { repo, revision, file, uri } = makeAFile(workspaceDir); - const converted = handleResponseUri(workspaceHandler, uri); - expect(converted).toBe(`git://${repo}/blob/${revision}/${file}`); -}); - -test('should support spaces in workspace dir', async () => { - const workspaceHasSpaces = path.join(baseDir, 'work space'); - const workspaceHandler = new WorkspaceHandler( - gitOps, - workspaceHasSpaces, - // @ts-ignore - null, - new ConsoleLoggerFactory() - ); - const { repo, revision, file, uri } = makeAFile(workspaceHasSpaces); - const converted = handleResponseUri(workspaceHandler, uri); - expect(converted).toBe(`git://${repo}/blob/${revision}/${file}`); -}); - -// FLAKY: https://github.com/elastic/kibana/issues/43655 -test('should support case-insensitive workspace dir', async () => { - const workspaceCaseInsensitive = path.join(baseDir, 'CamelCaseWorkSpace'); - // test only if it's case-insensitive - const workspaceHandler = new WorkspaceHandler( - gitOps, - workspaceCaseInsensitive, - // @ts-ignore - null, - new ConsoleLoggerFactory() - ); - const { repo, revision, file, uri } = makeAFile(workspaceCaseInsensitive); - // normally this test won't run on linux since its filesystem are case sensitive - // So there is no 'CAMELCASEWORKSPACE' folder. - if (fs.existsSync(workspaceCaseInsensitive.toUpperCase())) { - const converted = handleResponseUri(workspaceHandler, uri.toLocaleLowerCase()); - // workspace dir should be stripped - expect(converted).toBe( - `git://${repo.toLocaleLowerCase()}/blob/${revision.toLocaleLowerCase()}/${file.toLocaleLowerCase()}` - ); - } -}); - -test('should throw a error if url is invalid', async () => { - const workspaceHandler = new WorkspaceHandler( - gitOps, - workspaceDir, - // @ts-ignore - null, - new ConsoleLoggerFactory() - ); - const invalidDir = path.join(baseDir, 'invalid_dir'); - const { uri } = makeAFile(invalidDir); - expect(() => handleResponseUri(workspaceHandler, uri)).toThrow(); -}); - -beforeAll(() => { - fs.mkdirSync(workspaceDir, { recursive: true }); - fs.mkdirSync(repoDir, { recursive: true }); -}); - -afterAll(() => { - rimraf.sync(baseDir); -}); diff --git a/x-pack/legacy/plugins/code/server/lsp/workspace_handler.ts b/x-pack/legacy/plugins/code/server/lsp/workspace_handler.ts deleted file mode 100644 index 052f38cdcdb81..0000000000000 --- a/x-pack/legacy/plugins/code/server/lsp/workspace_handler.ts +++ /dev/null @@ -1,441 +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 Boom from 'boom'; -import del from 'del'; -import fs from 'fs'; -import { delay } from 'lodash'; -import path from 'path'; -import crypto from 'crypto'; -import { ResponseMessage } from 'vscode-jsonrpc/lib/messages'; -import { Hover, Location, TextDocumentPositionParams } from 'vscode-languageserver'; - -import { DetailSymbolInformation, Full } from '@elastic/lsp-extension'; - -import { SimpleGit } from '@elastic/simple-git/dist/promise'; -import { simplegit } from '@elastic/simple-git/dist'; -import { RepositoryUtils } from '../../common/repository_utils'; -import { parseLspUrl } from '../../common/uri_util'; -import { FileTreeItemType, LspRequest, WorkerReservedProgress } from '../../model'; -import { GitOperations, HEAD } from '../git_operations'; -import { EsClient } from '../lib/esqueue'; -import { Logger } from '../log'; -import { RepositoryObjectClient } from '../search'; -import { LoggerFactory } from '../utils/log_factory'; - -interface Worktree { - path: string; - revision: string; - branch: string; -} - -export const MAX_RESULT_COUNT = 20; - -export class WorkspaceHandler { - private revisionMap: { [uri: string]: string } = {}; - private log: Logger; - private readonly objectClient: RepositoryObjectClient | undefined = undefined; - - constructor( - public readonly gitOps: GitOperations, - private readonly workspacePath: string, - private readonly client: EsClient, - loggerFactory: LoggerFactory - ) { - // this.git = new GitOperations(repoPath); - this.log = loggerFactory.getLogger(['LSP', 'workspace']); - if (this.client) { - this.objectClient = new RepositoryObjectClient(this.client); - } - } - - /** - * open workspace for repositoryUri, update it from bare repository if necessary. - * @param repositoryUri the uri of bare repository. - * @param ref - */ - public async openWorkspace(repositoryUri: string, ref: string) { - // Try get repository clone status with 3 retries at maximum. - const tryGetGitStatus = async (retryCount: number) => { - let gitStatus; - try { - gitStatus = await this.objectClient!.getRepositoryGitStatus(repositoryUri); - } catch (error) { - throw Boom.internal(`checkout workspace on an unknown status repository`); - } - - if ( - !RepositoryUtils.hasFullyCloned(gitStatus.cloneProgress) && - gitStatus.progress < WorkerReservedProgress.COMPLETED - ) { - if (retryCount < 3) { - this.log.debug(`Check repository ${repositoryUri} clone status at trial ${retryCount}`); - return delay(tryGetGitStatus, 3000, retryCount + 1); - } else { - throw Boom.internal(`repository has not been fully cloned yet.`); - } - } - }; - if (this.objectClient) { - await tryGetGitStatus(0); - } - const git = await this.gitOps.openGit(repositoryUri); - const defaultBranch = await this.gitOps.getDefaultBranch(repositoryUri); - const targetRevision = await this.gitOps.getRevision(repositoryUri, ref); - if (ref !== defaultBranch) { - await this.checkCommit(git, targetRevision); - ref = defaultBranch; - } - const workspaceBranch = this.workspaceWorktreeBranchName(ref); - const worktrees = await this.listWorktrees(git); - let wt: Worktree; - if (worktrees.has(workspaceBranch)) { - wt = worktrees.get(workspaceBranch)!; - } else { - wt = await this.openWorktree( - git, - workspaceBranch, - await this.revisionDir(repositoryUri, ref, this.randomPath()), - targetRevision - ); - } - if (!targetRevision.startsWith(wt.revision)) { - await this.setWorkspaceRevision(wt.path, targetRevision); - } - return { - workspaceDir: wt.path, - workspaceRevision: targetRevision, - }; - } - - private randomPath() { - return crypto.randomBytes(4).toString('hex'); - } - - public async openWorktree( - git: SimpleGit, - workspaceBranch: string, - dir: string, - revision: string - ) { - await git.raw(['worktree', 'add', '-b', workspaceBranch, dir, revision]); - return { - revision, - path: dir, - branch: workspaceBranch, - } as Worktree; - } - - public async listWorkspaceFolders(repoUri: string) { - const git = await this.gitOps.openGit(repoUri); - const worktrees = await this.listWorktrees(git); - const isDir = (source: string) => fs.lstatSync(source).isDirectory(); - return [...worktrees.values()] - .filter(wt => wt.branch.startsWith('workspace')) - .map(wt => wt.path) - .filter(isDir); - } - - public async listWorktrees(git: SimpleGit): Promise> { - const str = await git.raw(['worktree', 'list']); - const regex = /(.*?)\s+([a-h0-9]+)\s+\[(.+)\]/gm; - let m; - const result: Map = new Map(); - while ((m = regex.exec(str)) !== null) { - if (m.index === regex.lastIndex) { - regex.lastIndex++; - } - const [, p, revision, branch] = m; - result.set(branch, { - path: p, - revision, - branch, - }); - } - return result; - } - - public async clearWorkspace(repoUri: string) { - const git = await this.gitOps.openGit(repoUri); - const worktrees = await this.listWorktrees(git); - for (const wt of worktrees.values()) { - await git.raw(['worktree', 'remove', wt.path, '--force']); - await git.deleteLocalBranch(wt.branch); - } - const workspaceDir = await this.workspaceDir(repoUri); - await del([workspaceDir], { force: true }); - } - - public async handleRequest(request: LspRequest): Promise { - const { method, params } = request; - switch (method) { - case 'textDocument/edefinition': - case 'textDocument/definition': - case 'textDocument/hover': - case 'textDocument/references': - case 'textDocument/documentSymbol': - case 'textDocument/full': { - const payload: TextDocumentPositionParams = params; - const { filePath, workspacePath, workspaceRevision } = await this.resolveUri( - params.textDocument.uri - ); - if (filePath) { - request.documentUri = payload.textDocument.uri; - payload.textDocument.uri = request.resolvedFilePath = filePath; - request.workspacePath = workspacePath!; - request.workspaceRevision = workspaceRevision!; - } - break; - } - default: - // do nothing - } - } - - public handleResponse(request: LspRequest, response: ResponseMessage): ResponseMessage { - if (!response.result) { - return response; - } - const { method } = request; - switch (method) { - case 'textDocument/hover': { - const result = response.result as Hover; - this.handleHoverContents(result); - return response; - } - case 'textDocument/edefinition': { - let result = response.result; - if (result) { - if (!Array.isArray(result)) { - response.result = result = [result]; - } - for (const def of result) { - this.convertLocation(def.location); - } - } - return response; - } - case 'textDocument/definition': { - const result = response.result; - if (result) { - if (Array.isArray(result)) { - (result as Location[]).forEach(location => this.convertLocation(location)); - } else { - this.convertLocation(result); - } - } - return response; - } - case 'textDocument/full': { - // unify the result of full as a array. - const result = Array.isArray(response.result) - ? (response.result as Full[]) - : [response.result as Full]; - for (const full of result) { - if (full.symbols) { - for (const symbol of full.symbols) { - this.convertLocation(symbol.symbolInformation.location); - - if (symbol.contents !== null || symbol.contents !== undefined) { - this.handleHoverContents(symbol); - } - } - } - if (full.references) { - for (const reference of full.references) { - this.convertLocation(reference.location); - if (reference.target.location) { - this.convertLocation(reference.target.location); - } - } - } - } - response.result = result; - return response; - } - case 'textDocument/references': { - if (response.result) { - const locations = (response.result as Location[]).slice(0, MAX_RESULT_COUNT); - for (const location of locations) { - this.convertLocation(location); - } - response.result = locations; - } - return response; - } - case 'textDocument/documentSymbol': { - if (response.result) { - for (const symbol of response.result) { - this.convertLocation(symbol.location); - } - } - return response; - } - default: - return response; - } - } - - private handleHoverContents(result: Hover | DetailSymbolInformation) { - if (!Array.isArray(result.contents)) { - if (typeof result.contents === 'string') { - result.contents = [{ language: '', value: result.contents }]; - } else { - result.contents = [result.contents as { language: string; value: string }]; - } - } else { - result.contents = Array.from(result.contents).map(c => { - if (typeof c === 'string') { - return { language: '', value: c }; - } else { - return c; - } - }); - } - } - - private parseLocation(location: Location) { - const uri = location.uri; - const prefix = path.sep === '\\' ? 'file:///' : 'file://'; - if (uri && uri.startsWith(prefix)) { - const locationPath = fs.realpathSync(decodeURIComponent(uri.substring(prefix.length))); - const workspacePath = fs.realpathSync(decodeURIComponent(this.workspacePath)); - // On windows, it's possible one path has c:\ and another has C:\, so we need compare case-insensitive - if (locationPath.toLocaleLowerCase().startsWith(workspacePath.toLocaleLowerCase())) { - let relativePath = locationPath.substring(workspacePath.length + 1); - if (path.sep === '\\') { - relativePath = relativePath.replace(/\\/gi, '/'); - } - const regex = /^(.*?\/.*?\/.*?)\/(__.*?\/)?([^_]+?)\/(.*)$/; - const m = relativePath.match(regex); - if (m) { - const repoUri = m[1]; - const revision = m[3]; - const gitRevision = this.revisionMap[`${repoUri}/${revision}`] || revision; - const file = m[4]; - return { repoUri, revision: gitRevision, file }; - } - } - // @ts-ignore - throw new Error("path in response doesn't not starts with workspace path"); - } - return null; - } - - private convertLocation(location: Location) { - if (location) { - const parsedLocation = this.parseLocation(location); - if (parsedLocation) { - const { repoUri, revision, file } = parsedLocation; - location.uri = `git://${repoUri}/blob/${revision}/${file}`; - } - return parsedLocation; - } - } - - private fileUrl(str: string) { - let pathName = str.replace(/\\/g, '/'); - // Windows drive letter must be prefixed with a slash - if (pathName[0] !== '/') { - pathName = '/' + pathName; - } - return 'file://' + pathName; - } - - /** - * convert a git uri to absolute file path, checkout code into workspace - * @param uri the uri - */ - private async resolveUri(uri: string) { - if (uri.startsWith('git://')) { - const { repoUri, file, revision } = parseLspUrl(uri)!; - const { workspaceDir, workspaceRevision } = await this.openWorkspace(repoUri, revision); - if (file) { - const isValidPath = await this.checkFile(repoUri, revision, file); - if (!isValidPath) { - throw new Error('invalid file path in requests.'); - } - } - return { - workspacePath: workspaceDir, - filePath: this.fileUrl(path.resolve(workspaceDir, file || '/')), - uri, - workspaceRevision, - }; - } else { - return { - workspacePath: undefined, - workspaceRevision: undefined, - filePath: undefined, - uri, - }; - } - } - - private async checkCommit(git: SimpleGit, targetRevision: string) { - // we only support headCommit now. - const headRevision = await git.revparse([HEAD]); - if (headRevision !== targetRevision) { - throw Boom.badRequest(`revision must be master.`); - } - } - - public async revisionDir(repositoryUri: string, ref: string, randomStr: string = '') { - return path.join(await this.workspaceDir(repositoryUri, randomStr), ref); - } - - private async workspaceDir(repoUri: string, randomStr: string = '') { - const base = path.join(this.workspacePath, repoUri); - if (randomStr === '') { - const git = await this.gitOps.openGit(repoUri); - const trees = await this.listWorktrees(git); - if (trees.size > 0) { - const wt = trees.values().next().value; - return path.dirname(wt.path); - } - } - if (randomStr) { - return path.join(base, `__${randomStr}`); - } else { - return base; - } - } - - private workspaceWorktreeBranchName(branch: string): string { - return `workspace-${branch}`; - } - - private async setWorkspaceRevision(workspaceDir: string, revision: string) { - const git = simplegit(workspaceDir); - await git.reset(['--hard', revision]); - } - - /** - * check whether the file path specify in the request is valid. The file path must: - * 1. exists in git repo - * 2. is a valid file or dir, can't be a link or submodule - * - * @param repoUri - * @param revision - * @param filePath - */ - private async checkFile(repoUri: string, revision: string, filePath: string) { - try { - const git = await this.gitOps.openGit(repoUri); - const p = filePath.endsWith('/') ? filePath.slice(0, -1) : filePath; - const tree = await this.gitOps.readTree(git, revision, p); - if (tree.entries.length !== 1) { - return false; - } - const entry = tree.entries[0]; - const type = GitOperations.mode2type(entry.mode); - return type === FileTreeItemType.File || type === FileTreeItemType.Directory; - } catch (e) { - // filePath may not exists - return false; - } - } -} diff --git a/x-pack/legacy/plugins/code/server/plugin.ts b/x-pack/legacy/plugins/code/server/plugin.ts deleted file mode 100644 index 737d0b5c6686b..0000000000000 --- a/x-pack/legacy/plugins/code/server/plugin.ts +++ /dev/null @@ -1,343 +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 crypto from 'crypto'; -import * as _ from 'lodash'; -import { CoreSetup, IRouter } from 'src/core/server'; - -import { RepositoryIndexInitializerFactory, tryMigrateIndices } from './indexer'; -import { Esqueue } from './lib/esqueue'; -import { Logger } from './log'; -import { JAVA } from './lsp/language_servers'; -import { LspService } from './lsp/lsp_service'; -import { RepositoryConfigController } from './repository_config_controller'; -import { IndexScheduler, UpdateScheduler } from './scheduler'; -import { CodeServerRouter } from './security'; -import { ServerOptions } from './server_options'; -import { - checkCodeNode, - checkRoute, - commitSearchRoute, - documentSearchRoute, - fileRoute, - installRoute, - lspRoute, - repositoryRoute, - repositorySearchRoute, - setupRoute, - statusRoute, - symbolByQnameRoute, - symbolSearchRoute, - workspaceRoute, -} from './routes'; -import { CodeServices } from './distributed/code_services'; -import { CodeNodeAdapter } from './distributed/multinode/code_node_adapter'; -import { LocalHandlerAdapter } from './distributed/local_handler_adapter'; -import { NonCodeNodeAdapter } from './distributed/multinode/non_code_node_adapter'; -import { - GitServiceDefinition, - GitServiceDefinitionOption, - LspServiceDefinition, - LspServiceDefinitionOption, - RepositoryServiceDefinition, - SetupDefinition, - WorkspaceDefinition, -} from './distributed/apis'; -import { initEs } from './init_es'; -import { initLocalService } from './init_local'; -import { initQueue } from './init_queue'; -import { initWorkers } from './init_workers'; -import { ClusterNodeAdapter } from './distributed/cluster/cluster_node_adapter'; -import { NodeRepositoriesService } from './distributed/cluster/node_repositories_service'; -import { initCodeUsageCollector } from './usage_collector'; -import { PluginSetupContract } from '../../../../plugins/code/server/index'; - -declare module 'src/core/server' { - interface RequestHandlerContext { - code: { - codeServices: CodeServices | null; - // @deprecated - legacy: { - securityPlugin: any; - }; - }; - } -} - -export class CodePlugin { - private isCodeNode = false; - - private queue: Esqueue | null = null; - private log: Logger; - private serverOptions: ServerOptions; - private indexScheduler: IndexScheduler | null = null; - private updateScheduler: UpdateScheduler | null = null; - private lspService: LspService | null = null; - private codeServices: CodeServices | null = null; - private nodeService: NodeRepositoriesService | null = null; - - private rndString: string | null = null; - private router: IRouter | null = null; - - constructor(private readonly initContext: PluginSetupContract) { - this.log = {} as Logger; - this.serverOptions = {} as ServerOptions; - } - - public async setup(core: CoreSetup, npHttp: any) { - const { server } = core.http as any; - this.serverOptions = new ServerOptions(this.initContext.legacy.config, server.config()); - this.log = new Logger(this.initContext.legacy.logger, this.serverOptions.verbose); - - this.router = npHttp.createRouter(); - this.rndString = crypto.randomBytes(20).toString('hex'); - - npHttp.registerRouteHandlerContext('code', () => { - return { - codeServices: this.codeServices, - legacy: { - securityPlugin: server.plugins.security, - }, - }; - }); - } - - // TODO: CodeStart will not have the register route api. - // Let's make it CoreSetup as the param for now. - public async start(core: CoreSetup) { - // called after all plugins are set up - const { server } = core.http as any; - const codeServerRouter = new CodeServerRouter(this.router!); - const codeNodeUrl = this.serverOptions.codeNodeUrl; - - checkRoute(this.router!, this.rndString!); - - if (this.serverOptions.clusterEnabled) { - this.initDevMode(server); - this.codeServices = await this.initClusterNode(server, codeServerRouter); - } else if (codeNodeUrl) { - const checkResult = await this.retryUntilAvailable( - async () => await checkCodeNode(codeNodeUrl, this.log, this.rndString!), - 5000 - ); - if (checkResult.me) { - const codeServices = new CodeServices(new CodeNodeAdapter(codeServerRouter, this.log)); - this.log.info('Initializing Code plugin as code-node.'); - this.codeServices = await this.initCodeNode(server, codeServices); - } else { - this.codeServices = await this.initNonCodeNode(codeNodeUrl, core); - } - } else { - const codeServices = new CodeServices(new LocalHandlerAdapter()); - // codeNodeUrl not set, single node mode - this.log.info('Initializing Code plugin as single-node.'); - this.initDevMode(server); - this.codeServices = await this.initCodeNode(server, codeServices); - } - await this.codeServices.start(); - } - - private async initClusterNode(server: any, codeServerRouter: CodeServerRouter) { - this.log.info('Initializing Code plugin as cluster-node'); - const { esClient, repoConfigController, repoIndexInitializerFactory } = await initEs( - this.initContext.legacy.elasticsearch.adminClient$, - this.log - ); - const clusterNodeAdapter = new ClusterNodeAdapter( - codeServerRouter, - this.log, - this.serverOptions, - esClient - ); - - const codeServices = new CodeServices(clusterNodeAdapter); - - this.queue = initQueue(this.serverOptions, this.log, esClient); - - const { gitOps, lspService } = initLocalService( - server, - this.initContext.legacy.logger, - this.serverOptions, - codeServices, - esClient, - repoConfigController - ); - this.lspService = lspService; - const { indexScheduler, updateScheduler, cloneWorker } = initWorkers( - this.log, - esClient, - this.queue!, - lspService, - gitOps, - this.serverOptions, - codeServices - ); - this.indexScheduler = indexScheduler; - this.updateScheduler = updateScheduler; - - this.nodeService = new NodeRepositoriesService( - this.log, - clusterNodeAdapter.clusterService, - clusterNodeAdapter.clusterMembershipService, - cloneWorker - ); - await this.nodeService.start(); - - this.initRoutes(server, codeServices, repoIndexInitializerFactory, repoConfigController); - - // Execute index version checking and try to migrate index data if necessary. - await tryMigrateIndices(esClient, this.log); - - return codeServices; - } - - private async initCodeNode(server: any, codeServices: CodeServices) { - this.isCodeNode = true; - const { esClient, repoConfigController, repoIndexInitializerFactory } = await initEs( - this.initContext.legacy.elasticsearch.adminClient$, - this.log - ); - - this.queue = initQueue(this.serverOptions, this.log, esClient); - - const { gitOps, lspService } = initLocalService( - server, - this.initContext.legacy.logger, - this.serverOptions, - codeServices, - esClient, - repoConfigController - ); - this.lspService = lspService; - const { indexScheduler, updateScheduler } = initWorkers( - this.log, - esClient, - this.queue!, - lspService, - gitOps, - this.serverOptions, - codeServices - ); - this.indexScheduler = indexScheduler; - this.updateScheduler = updateScheduler; - - this.initRoutes(server, codeServices, repoIndexInitializerFactory, repoConfigController); - - // TODO: extend the usage collection to cluster mode. - initCodeUsageCollector(server, esClient, lspService); - - // Execute index version checking and try to migrate index data if necessary. - await tryMigrateIndices(esClient, this.log); - - return codeServices; - } - - public async stop() { - if (this.isCodeNode) { - if (this.indexScheduler) this.indexScheduler.stop(); - if (this.updateScheduler) this.updateScheduler.stop(); - if (this.queue) this.queue.destroy(); - if (this.lspService) await this.lspService.shutdown(); - } - if (this.codeServices) { - await this.codeServices.stop(); - } - if (this.nodeService) { - await this.nodeService.stop(); - } - } - - private async initNonCodeNode(url: string, core: CoreSetup) { - const { server } = core.http as any; - this.log.info( - `Initializing Code plugin as non-code node, redirecting all code requests to ${url}` - ); - const codeServices = new CodeServices(new NonCodeNodeAdapter(url, this.log)); - codeServices.registerHandler(GitServiceDefinition, null, GitServiceDefinitionOption); - codeServices.registerHandler(RepositoryServiceDefinition, null); - codeServices.registerHandler(LspServiceDefinition, null, LspServiceDefinitionOption); - codeServices.registerHandler(WorkspaceDefinition, null); - codeServices.registerHandler(SetupDefinition, null); - const { repoConfigController, repoIndexInitializerFactory } = await initEs( - this.initContext.legacy.elasticsearch.adminClient$, - this.log - ); - this.initRoutes(server, codeServices, repoIndexInitializerFactory, repoConfigController); - return codeServices; - } - - private async initRoutes( - server: any, - codeServices: CodeServices, - repoIndexInitializerFactory: RepositoryIndexInitializerFactory, - repoConfigController: RepositoryConfigController - ) { - const codeServerRouter = new CodeServerRouter(this.router!); - repositoryRoute( - codeServerRouter, - codeServices, - repoIndexInitializerFactory, - repoConfigController, - this.serverOptions, - this.log - ); - repositorySearchRoute(codeServerRouter, this.log); - if (this.serverOptions.enableCommitIndexing) { - commitSearchRoute(codeServerRouter, this.log); - } - documentSearchRoute(codeServerRouter, this.log); - symbolSearchRoute(codeServerRouter, this.log); - fileRoute(codeServerRouter, codeServices); - workspaceRoute(codeServerRouter, this.serverOptions, codeServices); - symbolByQnameRoute(codeServerRouter, this.log); - installRoute(server, codeServerRouter, codeServices, this.serverOptions); - lspRoute(codeServerRouter, codeServices, this.serverOptions, this.log); - setupRoute(codeServerRouter, codeServices); - statusRoute(codeServerRouter, codeServices); - } - - private async retryUntilAvailable( - func: () => Promise, - intervalMs: number, - retries: number = Number.MAX_VALUE - ): Promise { - const value = await func(); - if (value) { - return value; - } else { - const promise = new Promise(resolve => { - const retry = () => { - func().then(v => { - if (v) { - resolve(v); - } else { - retries--; - if (retries > 0) { - setTimeout(retry, intervalMs); - } else { - resolve(v); - } - } - }); - }; - setTimeout(retry, intervalMs); - }); - return await promise; - } - } - - private initDevMode(server: any) { - // @ts-ignore - const devMode: boolean = this.serverOptions.devMode; - server.injectUiAppVars('code', () => ({ - enableLangserversDeveloping: devMode, - })); - // Enable the developing language servers in development mode. - if (devMode) { - JAVA.downloadUrl = _.partialRight(JAVA!.downloadUrl!, devMode); - } - } -} diff --git a/x-pack/legacy/plugins/code/server/poller.ts b/x-pack/legacy/plugins/code/server/poller.ts deleted file mode 100644 index e2803e9cf4b8a..0000000000000 --- a/x-pack/legacy/plugins/code/server/poller.ts +++ /dev/null @@ -1,103 +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. - */ - -/* - * Borrowed from https://github.com/elastic/kibana/blob/master/x-pack/legacy/common/poller.js - */ - -// Because the timers lib is global for Nodejs, it's not necessary to explicit import it. -// Also explicitly importing this lib is going to make Sinon fake timer fail to work. -// import { clearTimeout, setTimeout } from 'timers'; - -const noop = () => { - // noop -}; - -interface PollerOptions { - pollFrequencyInMillis: number; - functionToPoll: Poller['functionToPoll']; - successFunction?: Poller['successFunction']; - errorFunction?: Poller['errorFunction']; - trailing?: boolean; - continuePollingOnError?: boolean; - pollFrequencyErrorMultiplier?: number; -} - -export class Poller { - private readonly pollFrequencyInMillis: number; - private readonly functionToPoll: () => Promise; - private readonly successFunction: (result: T) => Promise | void; - private readonly errorFunction: (error: Error) => Promise | void; - private readonly trailing: boolean; - private readonly continuePollingOnError: boolean; - private readonly pollFrequencyErrorMultiplier: number; - - private timeoutId?: NodeJS.Timer; - - constructor(options: PollerOptions) { - this.functionToPoll = options.functionToPoll; // Must return a Promise - this.successFunction = options.successFunction || noop; - this.errorFunction = options.errorFunction || noop; - this.pollFrequencyInMillis = options.pollFrequencyInMillis; - this.trailing = options.trailing || false; - this.continuePollingOnError = options.continuePollingOnError || false; - this.pollFrequencyErrorMultiplier = options.pollFrequencyErrorMultiplier || 1; - } - - public getPollFrequency() { - return this.pollFrequencyInMillis; - } - - public start() { - if (this.isRunning()) { - return; - } - - if (this.trailing) { - this.timeoutId = global.setTimeout(this.poll.bind(this), this.pollFrequencyInMillis); - } else { - this.poll(); - } - } - - public stop() { - if (this.timeoutId) { - global.clearTimeout(this.timeoutId); - this.timeoutId = undefined; - } - } - - public isRunning() { - return !!this.timeoutId; - } - - private async poll() { - try { - await this.successFunction(await this.functionToPoll()); - - if (!this.isRunning()) { - return; - } - - this.timeoutId = global.setTimeout(this.poll.bind(this), this.pollFrequencyInMillis); - } catch (error) { - await this.errorFunction(error); - - if (!this.isRunning()) { - return; - } - - if (this.continuePollingOnError) { - this.timeoutId = global.setTimeout( - this.poll.bind(this), - this.pollFrequencyInMillis * this.pollFrequencyErrorMultiplier - ); - } else { - this.stop(); - } - } - } -} diff --git a/x-pack/legacy/plugins/code/server/queue/abstract_git_worker.ts b/x-pack/legacy/plugins/code/server/queue/abstract_git_worker.ts deleted file mode 100644 index 57d051f870f6d..0000000000000 --- a/x-pack/legacy/plugins/code/server/queue/abstract_git_worker.ts +++ /dev/null @@ -1,123 +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 { - CloneProgress, - CloneWorkerProgress, - CloneWorkerResult, - WorkerReservedProgress, - WorkerResult, -} from '../../model'; -import { DiskWatermarkService } from '../disk_watermark'; -import { GitOperations } from '../git_operations'; -import { EsClient, Esqueue } from '../lib/esqueue'; -import { Logger } from '../log'; -import { RepositoryObjectClient } from '../search'; -import { ServerOptions } from '../server_options'; -import { AbstractWorker } from './abstract_worker'; -import { CancellationReason } from './cancellation_service'; -import { Job } from './job'; - -export abstract class AbstractGitWorker extends AbstractWorker { - public id: string = 'abstract-git'; - protected objectClient: RepositoryObjectClient; - - constructor( - protected readonly queue: Esqueue, - protected readonly log: Logger, - protected readonly client: EsClient, - protected readonly serverOptions: ServerOptions, - protected readonly gitOps: GitOperations, - protected readonly watermarkService: DiskWatermarkService - ) { - super(queue, log); - this.objectClient = new RepositoryObjectClient(client); - } - - public async executeJob(job: Job): Promise { - const uri = job.payload.uri; - if (await this.watermarkService.isLowWatermark()) { - // Return job result as cancelled. - return { - uri, - cancelled: true, - cancelledReason: CancellationReason.LOW_DISK_SPACE, - }; - } - - return { uri }; - } - - public async onJobCompleted(job: Job, res: CloneWorkerResult) { - await super.onJobCompleted(job, res); - - // Update the default branch. - const repoUri = res.uri; - const revision = await this.gitOps.getHeadRevision(repoUri); - const defaultBranch = await this.gitOps.getDefaultBranch(repoUri); - - // Update the repository data. - try { - await this.objectClient.updateRepository(repoUri, { - defaultBranch, - revision, - }); - } catch (error) { - this.log.error(`Update repository default branch and revision error.`); - this.log.error(error); - } - - // Update the git operation status. - try { - return await this.objectClient.updateRepositoryGitStatus(repoUri, { - revision, - progress: WorkerReservedProgress.COMPLETED, - cloneProgress: { - isCloned: true, - }, - }); - } catch (error) { - this.log.error(`Update revision of repo clone done error.`); - this.log.error(error); - } - } - - public async updateProgress( - job: Job, - progress: number, - error?: Error, - cloneProgress?: CloneProgress - ) { - const { uri } = job.payload; - const p: CloneWorkerProgress = { - uri, - progress, - timestamp: new Date(), - cloneProgress, - errorMessage: error ? error.message : undefined, - }; - try { - return await this.objectClient.updateRepositoryGitStatus(uri, p); - } catch (err) { - // Do nothing here since it's not blocking anything. - // this.log.warn(`Update git clone progress error.`); - // this.log.warn(err); - } - } - - protected async onJobCancelled(job: Job, reason?: CancellationReason) { - if (reason && reason === CancellationReason.LOW_DISK_SPACE) { - // If the clone/update job is cancelled because of the disk watermark, manually - // trigger onJobExecutionError. - const msg = this.watermarkService.diskWatermarkViolationMessage(); - this.log.error( - 'Git clone/update job completed because of low disk space. Move forward as error.' - ); - const error = new Error(msg); - await this.onJobExecutionError({ job, error }); - } - } -} diff --git a/x-pack/legacy/plugins/code/server/queue/abstract_worker.ts b/x-pack/legacy/plugins/code/server/queue/abstract_worker.ts deleted file mode 100644 index c74f640a1d212..0000000000000 --- a/x-pack/legacy/plugins/code/server/queue/abstract_worker.ts +++ /dev/null @@ -1,158 +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 moment from 'moment'; - -import { WorkerReservedProgress, WorkerResult } from '../../model'; -import { - CancellationToken, - Esqueue, - events as esqueueEvents, - Job as JobInternal, -} from '../lib/esqueue'; -import { Logger } from '../log'; -import { Job } from './job'; -import { Worker } from './worker'; -import { CodeServices } from '../distributed/code_services'; - -export abstract class AbstractWorker implements Worker { - // The id of the worker. Also serves as the id of the job this worker consumes. - protected id = ''; - - constructor(protected readonly queue: Esqueue, protected readonly log: Logger) {} - - // Assemble jobs, for now most of the job object construction should be the same. - public async createJob(payload: any, options: any): Promise { - const timestamp = moment().valueOf(); - if (options.timeout !== undefined && options.timeout !== null) { - // If the job explicitly specify the timeout, then honor it. - return { - payload, - options, - timestamp, - }; - } else { - // Otherwise, use a default timeout. - const timeout = await this.getTimeoutMs(payload); - return { - payload, - options: { - ...options, - timeout, - }, - timestamp, - }; - } - } - - public async executeJob(job: Job): Promise { - // This is an abstract class. Do nothing here. You should override this. - return new Promise((resolve, _) => { - resolve(); - }); - } - - // Enqueue the job. - public async enqueueJob(payload: any, options: any) { - const job: Job = await this.createJob(payload, options); - return new Promise((resolve, reject) => { - const jobInternal: JobInternal = this.queue.addJob(this.id, job, job.options); - jobInternal.on(esqueueEvents.EVENT_JOB_CREATED, async (createdJob: JobInternal) => { - if (createdJob.id === jobInternal.id) { - await this.onJobEnqueued(job); - resolve(jobInternal); - } - }); - jobInternal.on(esqueueEvents.EVENT_JOB_CREATE_ERROR, reject); - }); - } - - public bind(codeServices: CodeServices) { - const workerFn = (payload: any, cancellationToken: CancellationToken) => { - const job: Job = { - ...payload, - cancellationToken, - }; - return this.executeJob(job); - }; - - const workerOptions = { - interval: 5000, - capacity: 5, - intervalErrorMultiplier: 1, - codeServices, - }; - - const queueWorker = this.queue.registerWorker(this.id, workerFn as any, workerOptions); - - queueWorker.on(esqueueEvents.EVENT_WORKER_COMPLETE, async (res: any) => { - const result: WorkerResult = res.output.content; - const job: Job = res.job; - await this.onJobCompleted(job, result); - }); - queueWorker.on(esqueueEvents.EVENT_WORKER_JOB_EXECUTION_ERROR, async (res: any) => { - await this.onJobExecutionError(res); - }); - queueWorker.on(esqueueEvents.EVENT_WORKER_JOB_TIMEOUT, async (res: any) => { - await this.onJobTimeOut(res); - }); - - return this; - } - - public async onJobEnqueued(job: Job) { - this.log.info(`${this.id} job enqueued with details ${JSON.stringify(job)}`); - return await this.updateProgress(job, WorkerReservedProgress.INIT); - } - - public async onJobCompleted(job: Job, res: WorkerResult) { - this.log.info( - `${this.id} job completed with result ${JSON.stringify( - res - )} in ${this.workerTaskDurationSeconds(job)} seconds.` - ); - if (res.cancelled) { - // Skip updating job progress if the job is done because of cancellation. - return; - } - return await this.updateProgress(job, WorkerReservedProgress.COMPLETED); - } - - public async onJobExecutionError(res: any) { - this.log.error( - `${this.id} job execution error ${JSON.stringify(res)} in ${this.workerTaskDurationSeconds( - res.job - )} seconds.` - ); - return await this.updateProgress(res.job, WorkerReservedProgress.ERROR, res.error); - } - - public async onJobTimeOut(res: any) { - this.log.error( - `${this.id} job timed out ${JSON.stringify(res)} in ${this.workerTaskDurationSeconds( - res.job - )} seconds.` - ); - return await this.updateProgress(res.job, WorkerReservedProgress.TIMEOUT, res.error); - } - - public async updateProgress(job: Job, progress: number, error?: Error) { - // This is an abstract class. Do nothing here. You should override this. - return new Promise((resolve, _) => { - resolve(); - }); - } - - protected async getTimeoutMs(payload: any) { - // Set to 1 hour by default. Override this function for sub classes if necessary. - return moment.duration(1, 'hour').asMilliseconds(); - } - - private workerTaskDurationSeconds(job: Job) { - const diff = moment().diff(moment(job.timestamp)); - return moment.duration(diff).asSeconds(); - } -} diff --git a/x-pack/legacy/plugins/code/server/queue/cancellation_service.test.ts b/x-pack/legacy/plugins/code/server/queue/cancellation_service.test.ts deleted file mode 100644 index 475925d7c82c3..0000000000000 --- a/x-pack/legacy/plugins/code/server/queue/cancellation_service.test.ts +++ /dev/null @@ -1,70 +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 { CancellationToken } from '../lib/esqueue'; - -import sinon from 'sinon'; - -import { CancellationReason, CancellationSerivce } from './cancellation_service'; - -afterEach(() => { - sinon.restore(); -}); - -test('Register and cancel cancellation token', async () => { - const repoUri = 'github.com/elastic/code'; - const service = new CancellationSerivce(); - const token = { - cancel: (): void => { - return; - }, - }; - const cancelSpy = sinon.spy(); - token.cancel = cancelSpy; - - // create a promise and defer its fulfillment - let promiseResolve: () => void = () => {}; - const promise = new Promise(resolve => { - promiseResolve = resolve; - }); - await service.registerCancelableIndexJob(repoUri, (token as any) as CancellationToken, promise); - // do not wait on the promise, or there will be a dead lock - const cancelPromise = service.cancelIndexJob(repoUri, CancellationReason.NEW_JOB_OVERRIDEN); - // resolve the promise now - promiseResolve(); - - await cancelPromise; - - expect(cancelSpy.calledOnce).toBeTruthy(); -}); - -test('Register and cancel cancellation token while an exception is thrown from the job', async () => { - const repoUri = 'github.com/elastic/code'; - const service = new CancellationSerivce(); - const token = { - cancel: (): void => { - return; - }, - }; - const cancelSpy = sinon.spy(); - token.cancel = cancelSpy; - - // create a promise and defer its rejection - let promiseReject: () => void = () => {}; - const promise = new Promise((resolve, reject) => { - promiseReject = reject; - }); - await service.registerCancelableIndexJob(repoUri, (token as any) as CancellationToken, promise); - // expect no exceptions are thrown when cancelling the job - // do not wait on the promise, or there will be a dead lock - const cancelPromise = service.cancelIndexJob(repoUri, CancellationReason.NEW_JOB_OVERRIDEN); - // reject the promise now - promiseReject(); - - await cancelPromise; - - expect(cancelSpy.calledOnce).toBeTruthy(); -}); diff --git a/x-pack/legacy/plugins/code/server/queue/cancellation_service.ts b/x-pack/legacy/plugins/code/server/queue/cancellation_service.ts deleted file mode 100644 index 0679ec7986c76..0000000000000 --- a/x-pack/legacy/plugins/code/server/queue/cancellation_service.ts +++ /dev/null @@ -1,102 +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 { RepositoryUri } from '../../model'; -import { CancellationToken } from '../lib/esqueue'; - -interface CancellableJob { - token: CancellationToken; - jobPromise: Promise; -} - -export enum CancellationReason { - REPOSITORY_DELETE = 'Cancel job because of deleting the entire repository', - LOW_DISK_SPACE = 'Cancel job because of low available disk space', - NEW_JOB_OVERRIDEN = 'Cancel job because of a new job of the same type has been registered', -} - -export class CancellationSerivce { - private cloneCancellationMap: Map; - private updateCancellationMap: Map; - private indexCancellationMap: Map; - - constructor() { - this.cloneCancellationMap = new Map(); - this.updateCancellationMap = new Map(); - this.indexCancellationMap = new Map(); - } - - public async cancelCloneJob(repoUri: RepositoryUri, reason: CancellationReason) { - await this.cancelJob(this.cloneCancellationMap, repoUri, reason); - } - - public async cancelUpdateJob(repoUri: RepositoryUri, reason: CancellationReason) { - await this.cancelJob(this.updateCancellationMap, repoUri, reason); - } - - public async cancelIndexJob(repoUri: RepositoryUri, reason: CancellationReason) { - await this.cancelJob(this.indexCancellationMap, repoUri, reason); - } - - public async registerCancelableCloneJob( - repoUri: RepositoryUri, - token: CancellationToken, - jobPromise: Promise - ) { - await this.registerCancelableJob(this.cloneCancellationMap, repoUri, token, jobPromise); - } - - public async registerCancelableUpdateJob( - repoUri: RepositoryUri, - token: CancellationToken, - jobPromise: Promise - ) { - await this.registerCancelableJob(this.updateCancellationMap, repoUri, token, jobPromise); - } - - public async registerCancelableIndexJob( - repoUri: RepositoryUri, - token: CancellationToken, - jobPromise: Promise - ) { - await this.registerCancelableJob(this.indexCancellationMap, repoUri, token, jobPromise); - } - - private async registerCancelableJob( - jobMap: Map, - repoUri: RepositoryUri, - token: CancellationToken, - jobPromise: Promise - ) { - // Try to cancel the job first. - await this.cancelJob(jobMap, repoUri, CancellationReason.NEW_JOB_OVERRIDEN); - jobMap.set(repoUri, { token, jobPromise }); - // remove the record from the cancellation service when the promise is fulfilled or rejected. - jobPromise.finally(() => { - jobMap.delete(repoUri); - }); - } - - private async cancelJob( - jobMap: Map, - repoUri: RepositoryUri, - reason: CancellationReason - ) { - const payload = jobMap.get(repoUri); - if (payload) { - const { token, jobPromise } = payload; - // 1. Use the cancellation token to pass cancel message to job - token.cancel(reason); - // 2. waiting on the actual job promise to be resolved - try { - await jobPromise; - } catch (e) { - // the exception from the job also indicates the job is finished, and it should be the duty of the worker for - // the job to handle it, so it's safe to just ignore the exception here - } - } - } -} diff --git a/x-pack/legacy/plugins/code/server/queue/clone_worker.ts b/x-pack/legacy/plugins/code/server/queue/clone_worker.ts deleted file mode 100644 index be9110c9f293c..0000000000000 --- a/x-pack/legacy/plugins/code/server/queue/clone_worker.ts +++ /dev/null @@ -1,176 +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 { delay } from 'lodash'; - -import { validateGitUrl } from '../../common/git_url_utils'; -import { RepositoryUtils } from '../../common/repository_utils'; -import { - CloneProgress, - CloneWorkerProgress, - CloneWorkerResult, - WorkerReservedProgress, -} from '../../model'; -import { DiskWatermarkService } from '../disk_watermark'; -import { GitOperations } from '../git_operations'; -import { EsClient, Esqueue } from '../lib/esqueue'; -import { Logger } from '../log'; -import { RepositoryServiceFactory } from '../repository_service_factory'; -import { ServerOptions } from '../server_options'; -import { AbstractGitWorker } from './abstract_git_worker'; -import { CancellationReason, CancellationSerivce } from './cancellation_service'; -import { IndexWorker } from './index_worker'; -import { Job } from './job'; - -export class CloneWorker extends AbstractGitWorker { - public id: string = 'clone'; - - constructor( - protected readonly queue: Esqueue, - protected readonly log: Logger, - protected readonly client: EsClient, - protected readonly serverOptions: ServerOptions, - protected readonly gitOps: GitOperations, - private readonly indexWorker: IndexWorker, - private readonly repoServiceFactory: RepositoryServiceFactory, - private readonly cancellationService: CancellationSerivce, - protected readonly watermarkService: DiskWatermarkService - ) { - super(queue, log, client, serverOptions, gitOps, watermarkService); - } - - public async executeJob(job: Job) { - const superRes = await super.executeJob(job); - if (superRes.cancelled) { - return superRes; - } - - const { payload, cancellationToken } = job; - const { url } = payload; - try { - validateGitUrl( - url, - this.serverOptions.security.gitHostWhitelist, - this.serverOptions.security.gitProtocolWhitelist - ); - } catch (error) { - this.log.error(`Validate git url ${url} error.`); - this.log.error(error); - return { - uri: url, - } as CloneWorkerResult; - } - - this.log.info(`Execute clone job for ${url}`); - const repoService = this.repoServiceFactory.newInstance( - this.serverOptions.repoPath, - this.serverOptions.credsPath, - this.log - ); - const repo = RepositoryUtils.buildRepository(url); - - // Try to cancel any existing clone job for this repository. - await this.cancellationService.cancelCloneJob(repo.uri, CancellationReason.NEW_JOB_OVERRIDEN); - - let cancelled = false; - let cancelledReason; - if (cancellationToken) { - cancellationToken.on((reason: string) => { - cancelled = true; - cancelledReason = reason; - }); - } - - const cloneJobPromise = repoService.clone( - repo, - async (progress: number, cloneProgress?: CloneProgress) => { - if (cancelled) { - // return false to stop the clone progress - return false; - } - - // Keep an eye on the disk usage during clone in case it goes above the - // disk watermark config. - if (await this.watermarkService.isLowWatermark()) { - // Cancel this clone job - if (cancellationToken) { - cancellationToken.cancel(CancellationReason.LOW_DISK_SPACE); - } - // return false to stop the clone progress - return false; - } - - // For clone job payload, it only has the url. Populate back the - // repository uri before update progress. - job.payload.uri = repo.uri; - this.updateProgress(job, progress, undefined, cloneProgress); - return true; - } - ); - - if (cancellationToken) { - await this.cancellationService.registerCancelableCloneJob( - repo.uri, - cancellationToken, - cloneJobPromise - ); - } - const res = await cloneJobPromise; - return { - ...res, - cancelled, - cancelledReason, - }; - } - - public async onJobCompleted(job: Job, res: CloneWorkerResult) { - if (res.cancelled) { - await this.onJobCancelled(job, res.cancelledReason); - // Skip updating job progress if the job is done because of cancellation. - return; - } - - const { uri, revision } = res.repo!; - this.log.info(`Clone job done for ${uri}`); - // For clone job payload, it only has the url. Populate back the - // repository uri. - job.payload.uri = uri; - await super.onJobCompleted(job, res); - - // Throw out a repository index request after 1 second. - return delay(async () => { - const payload = { uri, revision }; - await this.indexWorker.enqueueJob(payload, {}); - }, 1000); - } - - public async onJobEnqueued(job: Job) { - const { url } = job.payload; - const repo = RepositoryUtils.buildRepository(url); - const progress: CloneWorkerProgress = { - uri: repo.uri, - progress: WorkerReservedProgress.INIT, - timestamp: new Date(), - }; - return await this.objectClient.setRepositoryGitStatus(repo.uri, progress); - } - - public async onJobExecutionError(res: any) { - // The payload of clone job won't have the `uri`, but only with `url`. - const url = res.job.payload.url; - const repo = RepositoryUtils.buildRepository(url); - res.job.payload.uri = repo.uri; - return await super.onJobExecutionError(res); - } - - public async onJobTimeOut(res: any) { - // The payload of clone job won't have the `uri`, but only with `url`. - const url = res.job.payload.url; - const repo = RepositoryUtils.buildRepository(url); - res.job.payload.uri = repo.uri; - return await super.onJobTimeOut(res); - } -} diff --git a/x-pack/legacy/plugins/code/server/queue/delete_worker.test.ts b/x-pack/legacy/plugins/code/server/queue/delete_worker.test.ts deleted file mode 100644 index fd6de59ffa722..0000000000000 --- a/x-pack/legacy/plugins/code/server/queue/delete_worker.test.ts +++ /dev/null @@ -1,373 +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 sinon from 'sinon'; - -import { WorkerReservedProgress } from '../../model'; -import { RepositoryIndexStatusReservedField } from '../indexer/schema'; -import { EsClient, Esqueue } from '../lib/esqueue'; -import { GitOperations } from '../git_operations'; -import { Logger } from '../log'; -import { LspService } from '../lsp/lsp_service'; -import { RepositoryServiceFactory } from '../repository_service_factory'; -import { ServerOptions } from '../server_options'; -import { emptyAsyncFunc } from '../test_utils'; -import { ConsoleLoggerFactory } from '../utils/console_logger_factory'; -import { CancellationSerivce } from './cancellation_service'; -import { DeleteWorker } from './delete_worker'; - -const log: Logger = new ConsoleLoggerFactory().getLogger(['test']); - -const esQueue = {}; - -afterEach(() => { - sinon.restore(); -}); - -test('Execute delete job.', async () => { - // Setup RepositoryService - const removeSpy = sinon.fake.returns(Promise.resolve()); - const repoService = { - remove: emptyAsyncFunc, - }; - repoService.remove = removeSpy; - const repoServiceFactory = { - newInstance: (): void => { - return; - }, - }; - const newInstanceSpy = sinon.fake.returns(repoService); - repoServiceFactory.newInstance = newInstanceSpy; - - // Setup CancellationService - const cancelIndexJobSpy = sinon.spy(); - const cancelCloneJobSpy = sinon.spy(); - const cancelUpdateJobSpy = sinon.spy(); - const cancellationService = { - cancelCloneJob: emptyAsyncFunc, - cancelUpdateJob: emptyAsyncFunc, - cancelIndexJob: emptyAsyncFunc, - }; - cancellationService.cancelIndexJob = cancelIndexJobSpy; - cancellationService.cancelCloneJob = cancelCloneJobSpy; - cancellationService.cancelUpdateJob = cancelUpdateJobSpy; - - // Setup EsClient - const deleteSpy = sinon.fake.returns(Promise.resolve()); - const getSpy = sinon.fake.returns( - Promise.resolve({ - _source: { - [RepositoryIndexStatusReservedField]: { - uri: 'github.com/elastic/kibana', - progress: WorkerReservedProgress.COMPLETED, - timestamp: new Date(), - revision: 'abcdefg', - }, - }, - }) - ); - const esClient = { - indices: { - delete: emptyAsyncFunc, - }, - get: emptyAsyncFunc, - }; - esClient.indices.delete = deleteSpy; - esClient.get = getSpy; - - // Setup LspService - const deleteWorkspaceSpy = sinon.fake.returns(Promise.resolve()); - const lspService = { - deleteWorkspace: emptyAsyncFunc, - }; - lspService.deleteWorkspace = deleteWorkspaceSpy; - - // Setup GitOperations - const cleanRepoSpy = sinon.spy(); - const gitOps = { - cleanRepo: cleanRepoSpy, - }; - - const deleteWorker = new DeleteWorker( - esQueue as Esqueue, - log, - esClient as EsClient, - {} as ServerOptions, - (gitOps as any) as GitOperations, - (cancellationService as any) as CancellationSerivce, - (lspService as any) as LspService, - (repoServiceFactory as any) as RepositoryServiceFactory - ); - - await deleteWorker.executeJob({ - payload: { - uri: 'github.com/elastic/kibana', - }, - options: {}, - timestamp: 0, - }); - - expect(cancelIndexJobSpy.calledOnce).toBeTruthy(); - expect(cancelCloneJobSpy.calledOnce).toBeTruthy(); - expect(cancelUpdateJobSpy.calledOnce).toBeTruthy(); - - expect(newInstanceSpy.calledOnce).toBeTruthy(); - expect(removeSpy.calledOnce).toBeTruthy(); - expect(deleteSpy.calledThrice).toBeTruthy(); - - expect(deleteWorkspaceSpy.calledOnce).toBeTruthy(); -}); - -test('On delete job uri does not exist.', async () => { - // Setup RepositoryService - const removeSpy = sinon.fake.returns(Promise.resolve()); - const repoService = { - remove: emptyAsyncFunc, - }; - repoService.remove = removeSpy; - const repoServiceFactory = { - newInstance: (): void => { - return; - }, - }; - const newInstanceSpy = sinon.fake.returns(repoService); - repoServiceFactory.newInstance = newInstanceSpy; - - // Setup CancellationService - const cancelIndexJobSpy = sinon.spy(); - const cancelCloneJobSpy = sinon.spy(); - const cancelUpdateJobSpy = sinon.spy(); - const cancellationService = { - cancelCloneJob: emptyAsyncFunc, - cancelUpdateJob: emptyAsyncFunc, - cancelIndexJob: emptyAsyncFunc, - }; - cancellationService.cancelIndexJob = cancelIndexJobSpy; - cancellationService.cancelCloneJob = cancelCloneJobSpy; - cancellationService.cancelUpdateJob = cancelUpdateJobSpy; - - // Setup EsClient - const deleteSpy = sinon.fake.returns(Promise.resolve()); - const getSpy = sinon.fake.returns(Promise.reject(new Error('ES does not find the document'))); - const esClient = { - indices: { - delete: emptyAsyncFunc, - }, - get: emptyAsyncFunc, - }; - esClient.indices.delete = deleteSpy; - esClient.get = getSpy; - - // Setup LspService - const deleteWorkspaceSpy = sinon.fake.returns(Promise.resolve()); - const lspService = { - deleteWorkspace: emptyAsyncFunc, - }; - lspService.deleteWorkspace = deleteWorkspaceSpy; - - // Setup GitOperations - const cleanRepoSpy = sinon.spy(); - const gitOps = { - cleanRepo: cleanRepoSpy, - }; - - const deleteWorker = new DeleteWorker( - esQueue as Esqueue, - log, - esClient as EsClient, - { - security: {}, - } as ServerOptions, - (gitOps as any) as GitOperations, - (cancellationService as any) as CancellationSerivce, - (lspService as any) as LspService, - (repoServiceFactory as any) as RepositoryServiceFactory - ); - - const result = await deleteWorker.executeJob({ - payload: { - uri: 'repo/doesnot/exist', - }, - options: {}, - timestamp: 0, - }); - - expect(result.uri).toEqual('repo/doesnot/exist'); - expect(result.res).toBeTruthy(); - - // Non of them should be called. - expect(cancelIndexJobSpy.notCalled).toBeTruthy(); - expect(cancelCloneJobSpy.notCalled).toBeTruthy(); - expect(cancelUpdateJobSpy.notCalled).toBeTruthy(); - expect(newInstanceSpy.notCalled).toBeTruthy(); - expect(removeSpy.notCalled).toBeTruthy(); - expect(deleteSpy.notCalled).toBeTruthy(); - expect(deleteWorkspaceSpy.notCalled).toBeTruthy(); -}); - -test('On delete job uri contains ../', async () => { - // Setup RepositoryService - const removeSpy = sinon.fake.returns(Promise.resolve()); - const repoService = { - remove: emptyAsyncFunc, - }; - repoService.remove = removeSpy; - const repoServiceFactory = { - newInstance: (): void => { - return; - }, - }; - const newInstanceSpy = sinon.fake.returns(repoService); - repoServiceFactory.newInstance = newInstanceSpy; - - // Setup CancellationService - const cancelIndexJobSpy = sinon.spy(); - const cancelCloneJobSpy = sinon.spy(); - const cancelUpdateJobSpy = sinon.spy(); - const cancellationService = { - cancelCloneJob: emptyAsyncFunc, - cancelUpdateJob: emptyAsyncFunc, - cancelIndexJob: emptyAsyncFunc, - }; - cancellationService.cancelIndexJob = cancelIndexJobSpy; - cancellationService.cancelCloneJob = cancelCloneJobSpy; - cancellationService.cancelUpdateJob = cancelUpdateJobSpy; - - // Setup EsClient - const deleteSpy = sinon.fake.returns(Promise.resolve()); - const getSpy = sinon.fake.returns( - Promise.resolve({ - _source: { - [RepositoryIndexStatusReservedField]: { - uri: 'github.com/elastic/kibana/../../../../foo/bar', - progress: WorkerReservedProgress.COMPLETED, - timestamp: new Date(), - revision: 'abcdefg', - }, - }, - }) - ); - const esClient = { - indices: { - delete: emptyAsyncFunc, - }, - get: emptyAsyncFunc, - }; - esClient.indices.delete = deleteSpy; - esClient.get = getSpy; - - // Setup LspService - const deleteWorkspaceSpy = sinon.fake.returns(Promise.resolve()); - const lspService = { - deleteWorkspace: emptyAsyncFunc, - }; - lspService.deleteWorkspace = deleteWorkspaceSpy; - - // Setup GitOperations - const cleanRepoSpy = sinon.spy(); - const gitOps = { - cleanRepo: cleanRepoSpy, - }; - - const deleteWorker = new DeleteWorker( - esQueue as Esqueue, - log, - esClient as EsClient, - { - security: {}, - } as ServerOptions, - (gitOps as any) as GitOperations, - (cancellationService as any) as CancellationSerivce, - (lspService as any) as LspService, - (repoServiceFactory as any) as RepositoryServiceFactory - ); - - const result = await deleteWorker.executeJob({ - payload: { - uri: 'github.com/elastic/kibana/../../../../foo/bar', - }, - options: {}, - timestamp: 0, - }); - - expect(result.uri).toEqual('github.com/elastic/kibana/../../../../foo/bar'); - expect(result.res).toBeTruthy(); - - // Non of them should be called. - expect(cancelIndexJobSpy.notCalled).toBeTruthy(); - expect(cancelCloneJobSpy.notCalled).toBeTruthy(); - expect(cancelUpdateJobSpy.notCalled).toBeTruthy(); - expect(newInstanceSpy.notCalled).toBeTruthy(); - expect(removeSpy.notCalled).toBeTruthy(); - expect(deleteSpy.notCalled).toBeTruthy(); - expect(deleteWorkspaceSpy.notCalled).toBeTruthy(); -}); - -test('On delete job enqueued.', async () => { - // Setup EsClient - const indexSpy = sinon.fake.returns(Promise.resolve()); - const esClient = { - index: emptyAsyncFunc, - }; - esClient.index = indexSpy; - - const deleteWorker = new DeleteWorker( - esQueue as Esqueue, - log, - esClient as EsClient, - {} as ServerOptions, - {} as GitOperations, - {} as CancellationSerivce, - {} as LspService, - {} as RepositoryServiceFactory - ); - - await deleteWorker.onJobEnqueued({ - payload: { - uri: 'github.com/elastic/kibana', - }, - options: {}, - timestamp: 0, - }); - - expect(indexSpy.calledOnce).toBeTruthy(); -}); - -test('On delete job completed.', async () => { - // Setup EsClient - const updateSpy = sinon.fake.returns(Promise.resolve()); - const esClient = { - update: emptyAsyncFunc, - }; - esClient.update = updateSpy; - - const deleteWorker = new DeleteWorker( - esQueue as Esqueue, - log, - esClient as EsClient, - {} as ServerOptions, - {} as GitOperations, - {} as CancellationSerivce, - {} as LspService, - {} as RepositoryServiceFactory - ); - - await deleteWorker.onJobCompleted( - { - payload: { - uri: 'github.com/elastic/kibana', - }, - options: {}, - timestamp: 0, - }, - { - uri: 'github.com/elastic/kibana', - } - ); - - // Nothing is called. - expect(updateSpy.notCalled).toBeTruthy(); -}); diff --git a/x-pack/legacy/plugins/code/server/queue/delete_worker.ts b/x-pack/legacy/plugins/code/server/queue/delete_worker.ts deleted file mode 100644 index b17716e6b5d7e..0000000000000 --- a/x-pack/legacy/plugins/code/server/queue/delete_worker.ts +++ /dev/null @@ -1,167 +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 moment from 'moment'; - -import { RepositoryUri, WorkerReservedProgress } from '../../model'; -import { WorkerProgress } from '../../model/repository'; -import { GitOperations } from '../git_operations'; -import { DocumentIndexName, ReferenceIndexName, SymbolIndexName } from '../indexer/schema'; -import { EsClient, Esqueue } from '../lib/esqueue'; -import { Logger } from '../log'; -import { LspService } from '../lsp/lsp_service'; -import { RepositoryServiceFactory } from '../repository_service_factory'; -import { RepositoryObjectClient } from '../search'; -import { ServerOptions } from '../server_options'; -import { AbstractWorker } from './abstract_worker'; -import { CancellationReason, CancellationSerivce } from './cancellation_service'; -import { Job } from './job'; - -export class DeleteWorker extends AbstractWorker { - public id: string = 'delete'; - private objectClient: RepositoryObjectClient; - - constructor( - protected readonly queue: Esqueue, - protected readonly log: Logger, - protected readonly client: EsClient, - protected readonly serverOptions: ServerOptions, - protected readonly gitOps: GitOperations, - private readonly cancellationService: CancellationSerivce, - private readonly lspService: LspService, - private readonly repoServiceFactory: RepositoryServiceFactory - ) { - super(queue, log); - this.objectClient = new RepositoryObjectClient(this.client); - } - - public async executeJob(job: Job) { - const { uri } = job.payload; - - try { - // 1. Check if the uri exsits - await this.objectClient.getRepository(uri); - // 2. Double check if the uri contains ".." which could result in - // deleting incorrect files in the file systems. - if (uri.split('/').includes('..')) { - throw new Error('Repository URI should not contain "..".'); - } - } catch (error) { - this.log.error(`Invalid repository uri ${uri}. Skip delete job.`); - this.log.error(error); - return { - uri, - // Because the repository does not exist, we can regard the delete job to be successful. - res: true, - }; - } - - try { - // 1. Cancel running clone and update workers - await this.cancellationService.cancelCloneJob(uri, CancellationReason.REPOSITORY_DELETE); - await this.cancellationService.cancelUpdateJob(uri, CancellationReason.REPOSITORY_DELETE); - - // 2. Delete workspace and index workers. Since the indexing could be - // hanging in the initialization stage, we should delete the workspace - // to cancel it in the meantime. - const deleteWorkspacePromise = this.deletePromiseWrapper( - this.lspService.deleteWorkspace(uri), - 'workspace', - uri - ); - const indexJobCancelPromise = this.cancellationService.cancelIndexJob( - uri, - CancellationReason.REPOSITORY_DELETE - ); - await Promise.all([deleteWorkspacePromise, indexJobCancelPromise]); - - // 3. Delete ES indices and aliases - const deleteSymbolESIndexPromise = this.deletePromiseWrapper( - this.client.indices.delete({ index: `${SymbolIndexName(uri)}*` }), - 'symbol ES index', - uri - ); - - const deleteReferenceESIndexPromise = this.deletePromiseWrapper( - this.client.indices.delete({ index: `${ReferenceIndexName(uri)}*` }), - 'reference ES index', - uri - ); - await Promise.all([deleteSymbolESIndexPromise, deleteReferenceESIndexPromise]); - - const repoService = this.repoServiceFactory.newInstance( - this.serverOptions.repoPath, - this.serverOptions.credsPath, - this.log - ); - await this.deletePromiseWrapper(repoService.remove(uri), 'git data', uri); - - // 4. Delete the document index and alias where the repository document and all status reside, - // so that you won't be able to import the same repositories until they are - // fully removed. - await this.deletePromiseWrapper( - this.client.indices.delete({ index: `${DocumentIndexName(uri)}*` }), - 'document ES index', - uri - ); - - return { - uri, - res: true, - }; - } catch (error) { - this.log.error(`Delete repository ${uri} error.`); - this.log.error(error); - return { - uri, - res: false, - }; - } - } - - public async onJobEnqueued(job: Job) { - const repoUri = job.payload.uri; - const progress: WorkerProgress = { - uri: repoUri, - progress: WorkerReservedProgress.INIT, - timestamp: new Date(), - }; - return await this.objectClient.setRepositoryDeleteStatus(repoUri, progress); - } - - public async updateProgress(job: Job, progress: number) { - const { uri } = job.payload; - const p: WorkerProgress = { - uri, - progress, - timestamp: new Date(), - }; - if (progress !== WorkerReservedProgress.COMPLETED) { - return await this.objectClient.updateRepositoryDeleteStatus(uri, p); - } - } - - protected async getTimeoutMs(_: any) { - return ( - moment.duration(1, 'hour').asMilliseconds() + moment.duration(10, 'minutes').asMilliseconds() - ); - } - - private deletePromiseWrapper( - promise: Promise, - type: string, - repoUri: RepositoryUri - ): Promise { - return promise - .then(() => { - this.log.info(`Delete ${type} of repository ${repoUri} done.`); - }) - .catch((error: Error) => { - this.log.error(`Delete ${type} of repository ${repoUri} error.`); - this.log.error(error); - }); - } -} diff --git a/x-pack/legacy/plugins/code/server/queue/index.ts b/x-pack/legacy/plugins/code/server/queue/index.ts deleted file mode 100644 index 35de744d53c9b..0000000000000 --- a/x-pack/legacy/plugins/code/server/queue/index.ts +++ /dev/null @@ -1,12 +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 * from './cancellation_service'; -export * from './clone_worker'; -export * from './delete_worker'; -export * from './index_worker'; -export * from './update_worker'; -export * from './worker'; diff --git a/x-pack/legacy/plugins/code/server/queue/index_worker.test.ts b/x-pack/legacy/plugins/code/server/queue/index_worker.test.ts deleted file mode 100644 index 0c78a7abb3583..0000000000000 --- a/x-pack/legacy/plugins/code/server/queue/index_worker.test.ts +++ /dev/null @@ -1,523 +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 sinon from 'sinon'; - -import { IndexerType, WorkerReservedProgress } from '../../model'; -import { GitOperations } from '../git_operations'; -import { - RepositoryGitStatusReservedField, - RepositoryIndexStatusReservedField, -} from '../indexer/schema'; -import { CancellationToken, EsClient, Esqueue } from '../lib/esqueue'; -import { Logger } from '../log'; -import { emptyAsyncFunc } from '../test_utils'; -import { ConsoleLoggerFactory } from '../utils/console_logger_factory'; -import { CancellationReason, CancellationSerivce } from './cancellation_service'; -import { IndexWorker } from './index_worker'; - -const log: Logger = new ConsoleLoggerFactory().getLogger(['test']); - -const esQueue = {}; - -afterEach(() => { - sinon.restore(); -}); - -test('Execute index job.', async () => { - // Setup CancellationService - const cancelIndexJobSpy = sinon.spy(); - const registerCancelableIndexJobSpy = sinon.spy(); - const cancellationService = { - cancelIndexJob: emptyAsyncFunc, - registerCancelableIndexJob: emptyAsyncFunc, - }; - cancellationService.cancelIndexJob = cancelIndexJobSpy; - cancellationService.registerCancelableIndexJob = registerCancelableIndexJobSpy; - - // Setup EsClient - const getSpy = sinon.fake.returns( - Promise.resolve({ - _source: { - [RepositoryIndexStatusReservedField]: { - uri: 'github.com/elastic/kibana', - progress: WorkerReservedProgress.COMPLETED, - timestamp: new Date(), - revision: 'abcdefg', - }, - }, - }) - ); - const esClient = { - get: emptyAsyncFunc, - }; - esClient.get = getSpy; - - // Setup IndexerFactory - const cancelSpy = sinon.spy(); - const startSpy = sinon.fake.returns(new Map()); - const indexer = { - cancel: emptyAsyncFunc, - start: emptyAsyncFunc, - }; - indexer.cancel = cancelSpy; - indexer.start = startSpy; - const createSpy = sinon.fake.returns(indexer); - const indexerFactory = { - create: emptyAsyncFunc, - }; - indexerFactory.create = createSpy; - - const cToken = new CancellationToken(); - - const indexerFactoryMap = new Map(); - indexerFactoryMap.set(IndexerType.COMMIT, indexerFactory); - const indexWorker = new IndexWorker( - esQueue as Esqueue, - log, - esClient as EsClient, - indexerFactoryMap, - {} as GitOperations, - (cancellationService as any) as CancellationSerivce - ); - - await indexWorker.executeJob({ - payload: { - uri: 'github.com/elastic/kibana', - }, - options: {}, - cancellationToken: cToken, - timestamp: 0, - }); - - expect(cancelIndexJobSpy.calledOnce).toBeTruthy(); - expect(getSpy.calledOnce).toBeTruthy(); - expect(createSpy.calledOnce).toBeTruthy(); - expect(startSpy.calledOnce).toBeTruthy(); - expect(cancelSpy.notCalled).toBeTruthy(); -}); - -test('Execute index job and then cancel.', async () => { - // Setup CancellationService - const cancelIndexJobSpy = sinon.spy(); - const registerCancelableIndexJobSpy = sinon.spy(); - const cancellationService = { - cancelIndexJob: emptyAsyncFunc, - registerCancelableIndexJob: emptyAsyncFunc, - }; - cancellationService.cancelIndexJob = cancelIndexJobSpy; - cancellationService.registerCancelableIndexJob = registerCancelableIndexJobSpy; - - // Setup EsClient - const getSpy = sinon.fake.returns( - Promise.resolve({ - _source: { - [RepositoryIndexStatusReservedField]: { - uri: 'github.com/elastic/kibana', - progress: WorkerReservedProgress.COMPLETED, - timestamp: new Date(), - revision: 'abcdefg', - }, - }, - }) - ); - const esClient = { - get: emptyAsyncFunc, - }; - esClient.get = getSpy; - - // Setup IndexerFactory - const cancelSpy = sinon.spy(); - const startSpy = sinon.fake.returns(new Map()); - const indexer = { - cancel: emptyAsyncFunc, - start: emptyAsyncFunc, - }; - indexer.cancel = cancelSpy; - indexer.start = startSpy; - const createSpy = sinon.fake.returns(indexer); - const indexerFactory = { - create: emptyAsyncFunc, - }; - indexerFactory.create = createSpy; - - const cToken = new CancellationToken(); - - const indexerFactoryMap = new Map(); - indexerFactoryMap.set(IndexerType.COMMIT, indexerFactory); - const indexWorker = new IndexWorker( - esQueue as Esqueue, - log, - esClient as EsClient, - indexerFactoryMap, - {} as GitOperations, - (cancellationService as any) as CancellationSerivce - ); - - await indexWorker.executeJob({ - payload: { - uri: 'github.com/elastic/kibana', - }, - options: {}, - cancellationToken: cToken, - timestamp: 0, - }); - - // Cancel the index job. - cToken.cancel(CancellationReason.REPOSITORY_DELETE); - - expect(cancelIndexJobSpy.calledOnce).toBeTruthy(); - expect(getSpy.calledOnce).toBeTruthy(); - expect(createSpy.calledOnce).toBeTruthy(); - expect(startSpy.calledOnce).toBeTruthy(); - // Then the the cancel function of the indexer should be called. - expect(cancelSpy.calledOnce).toBeTruthy(); -}); - -test('Index job skipped/deduplicated if revision matches', async () => { - // Setup CancellationService - const cancelIndexJobSpy = sinon.spy(); - const registerCancelableIndexJobSpy = sinon.spy(); - const cancellationService = { - cancelIndexJob: emptyAsyncFunc, - registerCancelableIndexJob: emptyAsyncFunc, - }; - cancellationService.cancelIndexJob = cancelIndexJobSpy; - cancellationService.registerCancelableIndexJob = registerCancelableIndexJobSpy; - - // Setup EsClient - const getSpy = sinon.fake.returns( - Promise.resolve({ - _source: { - [RepositoryIndexStatusReservedField]: { - uri: 'github.com/elastic/kibana', - progress: 50, - timestamp: new Date(), - revision: 'abcdefg', - indexProgress: {}, - }, - }, - }) - ); - const esClient = { - get: emptyAsyncFunc, - }; - esClient.get = getSpy; - - // Setup IndexerFactory - const cancelSpy = sinon.spy(); - const startSpy = sinon.fake.returns(new Map()); - const indexer = { - cancel: emptyAsyncFunc, - start: emptyAsyncFunc, - }; - indexer.cancel = cancelSpy; - indexer.start = startSpy; - const createSpy = sinon.fake.returns(indexer); - const indexerFactory = { - create: emptyAsyncFunc, - }; - indexerFactory.create = createSpy; - - const cToken = new CancellationToken(); - - const indexerFactoryMap = new Map(); - indexerFactoryMap.set(IndexerType.COMMIT, indexerFactory); - const indexWorker = new IndexWorker( - esQueue as Esqueue, - log, - esClient as EsClient, - indexerFactoryMap, - {} as GitOperations, - (cancellationService as any) as CancellationSerivce - ); - - await indexWorker.executeJob({ - payload: { - uri: 'github.com/elastic/kibana', - revision: 'abcdefg', - }, - options: {}, - cancellationToken: cToken, - timestamp: 0, - }); - - expect(getSpy.calledOnce).toBeTruthy(); - expect(cancelIndexJobSpy.notCalled).toBeTruthy(); - expect(createSpy.notCalled).toBeTruthy(); - expect(startSpy.notCalled).toBeTruthy(); - expect(cancelSpy.notCalled).toBeTruthy(); -}); - -test('Index job continue if revision matches and checkpoint found', async () => { - // Setup CancellationService - const cancelIndexJobSpy = sinon.spy(); - const registerCancelableIndexJobSpy = sinon.spy(); - const cancellationService = { - cancelIndexJob: emptyAsyncFunc, - registerCancelableIndexJob: emptyAsyncFunc, - }; - cancellationService.cancelIndexJob = cancelIndexJobSpy; - cancellationService.registerCancelableIndexJob = registerCancelableIndexJobSpy; - - // Setup EsClient - const getSpy = sinon.fake.returns( - Promise.resolve({ - _source: { - [RepositoryIndexStatusReservedField]: { - uri: 'github.com/elastic/kibana', - progress: 50, - timestamp: new Date(), - revision: 'abcdefg', - indexProgress: { - checkpoint: { - repoUri: 'github.com/elastic/kibana', - filePath: 'foo/bar.js', - revision: 'abcdefg', - }, - }, - }, - }, - }) - ); - const esClient = { - get: emptyAsyncFunc, - }; - esClient.get = getSpy; - - // Setup IndexerFactory - const cancelSpy = sinon.spy(); - const startSpy = sinon.fake.returns(new Map()); - const indexer = { - cancel: emptyAsyncFunc, - start: emptyAsyncFunc, - }; - indexer.cancel = cancelSpy; - indexer.start = startSpy; - const createSpy = sinon.fake.returns(indexer); - const indexerFactory = { - create: emptyAsyncFunc, - }; - indexerFactory.create = createSpy; - - const cToken = new CancellationToken(); - - const indexerFactoryMap = new Map(); - indexerFactoryMap.set(IndexerType.COMMIT, indexerFactory); - const indexWorker = new IndexWorker( - esQueue as Esqueue, - log, - esClient as EsClient, - indexerFactoryMap, - {} as GitOperations, - (cancellationService as any) as CancellationSerivce - ); - - await indexWorker.executeJob({ - payload: { - uri: 'github.com/elastic/kibana', - revision: 'abcdefg', - }, - options: {}, - cancellationToken: cToken, - timestamp: 0, - }); - - expect(getSpy.calledOnce).toBeTruthy(); - // the rest of the index worker logic after the checkpoint handling - // should be executed. - expect(cancelIndexJobSpy.calledOnce).toBeTruthy(); - expect(createSpy.calledOnce).toBeTruthy(); - expect(startSpy.calledOnce).toBeTruthy(); - expect(cancelSpy.notCalled).toBeTruthy(); -}); - -test('On index job enqueued.', async () => { - // Setup EsClient - const indexSpy = sinon.fake.returns(Promise.resolve()); - const esClient = { - index: emptyAsyncFunc, - }; - esClient.index = indexSpy; - - const indexWorker = new IndexWorker( - esQueue as Esqueue, - log, - esClient as EsClient, - new Map(), - {} as GitOperations, - {} as CancellationSerivce - ); - - await indexWorker.onJobEnqueued({ - payload: { - uri: 'github.com/elastic/kibana', - }, - options: {}, - timestamp: 0, - }); - - expect(indexSpy.calledOnce).toBeTruthy(); -}); - -test('On index job completed.', async () => { - // Setup EsClient - const updateSpy = sinon.fake.returns(Promise.resolve()); - const getSpy = sinon.fake.returns( - Promise.resolve({ - _source: { - [RepositoryGitStatusReservedField]: { - uri: 'github.com/elastic/kibana', - progress: WorkerReservedProgress.COMPLETED, - timestamp: new Date(), - revision: 'master', - }, - }, - }) - ); - const esClient = { - update: emptyAsyncFunc, - get: emptyAsyncFunc, - }; - esClient.update = updateSpy; - esClient.get = getSpy; - - const indexWorker = new IndexWorker( - esQueue as Esqueue, - log, - esClient as EsClient, - new Map(), - {} as GitOperations, - {} as CancellationSerivce - ); - - await indexWorker.onJobCompleted( - { - payload: { - uri: 'github.com/elastic/kibana', - revision: 'master', - }, - options: {}, - timestamp: 0, - }, - { - uri: 'github.com/elastic/kibana', - revision: 'master', - stats: new Map(), - } - ); - - expect(updateSpy.calledTwice).toBeTruthy(); -}); - -test('On index job completed because of cancellation.', async () => { - // Setup EsClient - const updateSpy = sinon.fake.returns(Promise.resolve()); - const getSpy = sinon.fake.returns( - Promise.resolve({ - _source: { - [RepositoryGitStatusReservedField]: { - uri: 'github.com/elastic/kibana', - progress: WorkerReservedProgress.COMPLETED, - timestamp: new Date(), - revision: 'master', - }, - }, - }) - ); - const esClient = { - update: emptyAsyncFunc, - get: emptyAsyncFunc, - }; - esClient.update = updateSpy; - esClient.get = getSpy; - - const indexWorker = new IndexWorker( - esQueue as Esqueue, - log, - esClient as EsClient, - new Map(), - {} as GitOperations, - {} as CancellationSerivce - ); - - await indexWorker.onJobCompleted( - { - payload: { - uri: 'github.com/elastic/kibana', - revision: 'master', - }, - options: {}, - timestamp: 0, - }, - { - uri: 'github.com/elastic/kibana', - revision: 'master', - stats: new Map(), - // Index job is done because of cancellation. - cancelled: true, - } - ); - - // The elasticsearch update won't be called for the sake of - // cancellation. - expect(updateSpy.notCalled).toBeTruthy(); -}); - -test('On index job completed with a different revision in git status.', async () => { - // Setup EsClient - const updateSpy = sinon.fake.returns(Promise.resolve()); - const getSpy = sinon.fake.returns( - Promise.resolve({ - _source: { - [RepositoryGitStatusReservedField]: { - uri: 'github.com/elastic/kibana', - progress: WorkerReservedProgress.COMPLETED, - timestamp: new Date(), - revision: 'new-revision', - }, - }, - }) - ); - const esClient = { - update: emptyAsyncFunc, - get: emptyAsyncFunc, - }; - esClient.update = updateSpy; - esClient.get = getSpy; - - const indexWorker = new IndexWorker( - {} as Esqueue, - log, - esClient as EsClient, - new Map(), - {} as GitOperations, - {} as CancellationSerivce - ); - const enqueueJobStub = sinon.stub(); - indexWorker.enqueueJob = enqueueJobStub; - - await indexWorker.onJobCompleted( - { - payload: { - uri: 'github.com/elastic/kibana', - revision: 'old-revision', - }, - options: {}, - timestamp: 0, - }, - { - uri: 'github.com/elastic/kibana', - revision: 'master', - stats: new Map(), - } - ); - - // An additional index job should be enqueued. - expect(enqueueJobStub.calledOnce).toBeTruthy(); - - expect(updateSpy.calledTwice).toBeTruthy(); -}); diff --git a/x-pack/legacy/plugins/code/server/queue/index_worker.ts b/x-pack/legacy/plugins/code/server/queue/index_worker.ts deleted file mode 100644 index 0f61605f99cf8..0000000000000 --- a/x-pack/legacy/plugins/code/server/queue/index_worker.ts +++ /dev/null @@ -1,288 +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 moment from 'moment'; - -import { - IndexProgress, - IndexRequest, - IndexStats, - IndexerType, - IndexWorkerProgress, - IndexWorkerResult, - RepositoryUri, - WorkerProgress, - WorkerReservedProgress, -} from '../../model'; -import { GitOperations, HEAD } from '../git_operations'; -import { IndexerFactory } from '../indexer'; -import { EsClient, Esqueue } from '../lib/esqueue'; -import { Logger } from '../log'; -import { RepositoryObjectClient } from '../search'; -import { aggregateIndexStats } from '../utils/index_stats_aggregator'; -import { AbstractWorker } from './abstract_worker'; -import { CancellationReason, CancellationSerivce } from './cancellation_service'; -import { Job } from './job'; - -export class IndexWorker extends AbstractWorker { - public id: string = 'index'; - private objectClient: RepositoryObjectClient; - - constructor( - protected readonly queue: Esqueue, - protected readonly log: Logger, - protected readonly client: EsClient, - protected readonly indexerFactories: Map, - protected readonly gitOps: GitOperations, - private readonly cancellationService: CancellationSerivce - ) { - super(queue, log); - - this.objectClient = new RepositoryObjectClient(this.client); - } - - public async executeJob(job: Job) { - const { payload, cancellationToken } = job; - const { uri, revision, enforceReindex } = payload; - const indexerNumber = this.indexerFactories.size; - - const { resume, checkpointReqs } = await this.shouldJobResume(payload); - if (!resume) { - this.log.info(`Index job skipped for ${uri} at revision ${revision}`); - return { - uri, - revision, - }; - } - - // Binding the index cancellation logic - let cancelled = false; - await this.cancellationService.cancelIndexJob(uri, CancellationReason.NEW_JOB_OVERRIDEN); - const indexPromises: Array> = Array.from( - this.indexerFactories.values() - ).map(async (factory: IndexerFactory) => { - const indexer = await factory.create(uri, revision, enforceReindex); - if (!indexer) { - this.log.info(`Failed to create indexer for ${uri}`); - return new Map(); // return an empty map as stats. - } - - if (cancellationToken) { - cancellationToken.on(() => { - indexer.cancel(); - cancelled = true; - }); - } - - const progressReporter = this.getProgressReporter(uri, revision, indexer.type, indexerNumber); - return indexer.start(progressReporter, checkpointReqs.get(indexer.type)); - }); - - const allPromise = Promise.all(indexPromises); - - if (cancellationToken) { - await this.cancellationService.registerCancelableIndexJob(uri, cancellationToken, allPromise); - } - - const stats: IndexStats[] = await allPromise; - const res: IndexWorkerResult = { - uri, - revision, - stats: aggregateIndexStats(stats), - cancelled, - }; - return res; - } - - public async onJobEnqueued(job: Job) { - const { uri, revision } = job.payload; - const progress: WorkerProgress = { - uri, - progress: WorkerReservedProgress.INIT, - timestamp: new Date(), - revision, - }; - return await this.objectClient.setRepositoryIndexStatus(uri, progress); - } - - public async onJobCompleted(job: Job, res: IndexWorkerResult) { - if (res.cancelled) { - // Skip updating job progress if the job is done because of cancellation. - return; - } - - this.log.info(`Index worker finished with stats: ${JSON.stringify([...res.stats])}`); - await super.onJobCompleted(job, res); - const { uri, revision } = job.payload; - try { - // Double check if the current revision is different from the origin reivsion. - // If so, kick off another index job to catch up the data descrepency. - const gitStatus = await this.objectClient.getRepositoryGitStatus(uri); - if (gitStatus.revision !== revision) { - const payload = { - uri, - revision: gitStatus.revision, - }; - await this.enqueueJob(payload, {}); - } - - return await this.objectClient.updateRepository(uri, { indexedRevision: revision }); - } catch (error) { - this.log.error(`Update indexed revision in repository object error.`); - this.log.error(error); - } - } - - public async updateProgress(job: Job, progress: number) { - const { uri } = job.payload; - let p: any = { - uri, - progress, - timestamp: new Date(), - }; - if ( - progress === WorkerReservedProgress.COMPLETED || - progress === WorkerReservedProgress.ERROR || - progress === WorkerReservedProgress.TIMEOUT - ) { - // Reset the checkpoints if necessary. - p = { - ...p, - indexProgress: { - checkpoint: null, - }, - commitIndexProgress: { - checkpoint: null, - }, - }; - } - try { - return await this.objectClient.updateRepositoryIndexStatus(uri, p); - } catch (error) { - this.log.error(`Update index progress error.`); - this.log.error(error); - } - } - - protected async getTimeoutMs(payload: any) { - try { - const totalCount = await this.gitOps.countRepoFiles(payload.uri, HEAD); - let timeout = moment.duration(1, 'hour').asMilliseconds(); - if (totalCount > 0) { - // timeout = ln(file_count) in hour - // e.g. 10 files -> 2.3 hours, 100 files -> 4.6 hours, 1000 -> 6.9 hours, 10000 -> 9.2 hours - timeout = moment.duration(Math.log(totalCount), 'hour').asMilliseconds(); - } - this.log.info(`Set index job timeout to be ${timeout} ms.`); - return timeout; - } catch (error) { - this.log.error(`Get repo file total count error.`); - this.log.error(error); - throw error; - } - } - - private getProgressReporter( - repoUri: RepositoryUri, - revision: string, - type: IndexerType, - total: number // total number of indexers - ) { - return async (progress: IndexProgress) => { - const indexStatus: IndexWorkerProgress = await this.objectClient.getRepositoryIndexStatus( - repoUri - ); - const p: IndexWorkerProgress = { - uri: repoUri, - progress: indexStatus.progress, - timestamp: new Date(), - revision, - }; - - switch (type) { - case IndexerType.COMMIT: - p.commitIndexProgress = progress; - p.progress = - progress.percentage + - (indexStatus.indexProgress ? indexStatus.indexProgress.percentage : 0); - break; - case IndexerType.LSP: - p.indexProgress = progress; - p.progress = - progress.percentage + - (indexStatus.commitIndexProgress ? indexStatus.commitIndexProgress.percentage : 0); - break; - default: - this.log.warn(`Unknown indexer type ${type} for indexing progress report.`); - break; - } - return await this.objectClient.updateRepositoryIndexStatus(repoUri, p); - }; - } - - // 1. Try to load checkpointed requests for all indexers to the `checkpointReqs` field - // of the return value. - // 2. Make a decision on if the index job should proceed indicate by the boolean `resume` - // field of the return value. - private async shouldJobResume( - payload: any - ): Promise<{ - resume: boolean; - checkpointReqs: Map; - }> { - const { uri, revision } = payload; - - const workerProgress = (await this.objectClient.getRepositoryIndexStatus( - uri - )) as IndexWorkerProgress; - - let lspCheckpointReq: IndexRequest | undefined; - let commitCheckpointReq: IndexRequest | undefined; - const checkpointReqs: Map = new Map(); - if (workerProgress) { - // There exist an ongoing index process - const { - uri: currentUri, - revision: currentRevision, - indexProgress: currentLspIndexProgress, - commitIndexProgress: currentCommitIndexProgress, - progress, - } = workerProgress; - - lspCheckpointReq = currentLspIndexProgress && currentLspIndexProgress.checkpoint; - commitCheckpointReq = currentCommitIndexProgress && currentCommitIndexProgress.checkpoint; - - if ( - !lspCheckpointReq && - !commitCheckpointReq && - progress > WorkerReservedProgress.INIT && - progress < WorkerReservedProgress.COMPLETED && - currentUri === uri && - currentRevision === revision - ) { - // Dedup this index job request if: - // 1. no checkpoint exist (undefined or empty string) for either LSP or commit indexer - // 2. index progress is ongoing - // 3. the uri and revision match the current job - return { - resume: false, - checkpointReqs, - }; - } - } - - // Add the checkpoints into the map as a result to return. They could be undefined. - checkpointReqs.set(IndexerType.LSP, lspCheckpointReq); - checkpointReqs.set(IndexerType.LSP_INC, lspCheckpointReq); - checkpointReqs.set(IndexerType.COMMIT, commitCheckpointReq); - checkpointReqs.set(IndexerType.COMMIT_INC, commitCheckpointReq); - - return { - resume: true, - checkpointReqs, - }; - } -} diff --git a/x-pack/legacy/plugins/code/server/queue/job.ts b/x-pack/legacy/plugins/code/server/queue/job.ts deleted file mode 100644 index 7ca328c128e65..0000000000000 --- a/x-pack/legacy/plugins/code/server/queue/job.ts +++ /dev/null @@ -1,14 +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 { CancellationToken } from '../lib/esqueue'; - -export interface Job { - payload: any; - options: any; - timestamp: number; - cancellationToken?: CancellationToken; -} diff --git a/x-pack/legacy/plugins/code/server/queue/update_worker.test.ts b/x-pack/legacy/plugins/code/server/queue/update_worker.test.ts deleted file mode 100644 index 808aa922d0f27..0000000000000 --- a/x-pack/legacy/plugins/code/server/queue/update_worker.test.ts +++ /dev/null @@ -1,286 +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 sinon from 'sinon'; - -import { EsClient, Esqueue } from '../lib/esqueue'; -import { Repository, UpdateWorkerResult } from '../../model'; -import { DiskWatermarkService } from '../disk_watermark'; -import { GitOperations } from '../git_operations'; -import { Logger } from '../log'; -import { RepositoryServiceFactory } from '../repository_service_factory'; -import { ServerOptions } from '../server_options'; -import { emptyAsyncFunc } from '../test_utils'; -import { ConsoleLoggerFactory } from '../utils/console_logger_factory'; -import { CancellationReason, CancellationSerivce } from './cancellation_service'; -import { UpdateWorker } from './update_worker'; - -const log: Logger = new ConsoleLoggerFactory().getLogger(['test']); - -const esClient = { - update: emptyAsyncFunc, - get: emptyAsyncFunc, -}; -const esQueue = {}; - -afterEach(() => { - sinon.restore(); -}); - -test('Execute update job', async () => { - // Setup RepositoryService - const updateSpy = sinon.spy(); - const repoService = { - update: emptyAsyncFunc, - }; - repoService.update = updateSpy; - const repoServiceFactory = { - newInstance: (): void => { - return; - }, - }; - const newInstanceSpy = sinon.fake.returns(repoService); - repoServiceFactory.newInstance = newInstanceSpy; - - // Setup CancellationService - const cancelUpdateJobSpy = sinon.spy(); - const registerCancelableUpdateJobSpy = sinon.spy(); - const cancellationService: any = { - cancelUpdateJob: emptyAsyncFunc, - registerCancelableUpdateJob: emptyAsyncFunc, - }; - cancellationService.cancelUpdateJob = cancelUpdateJobSpy; - cancellationService.registerCancelableUpdateJob = registerCancelableUpdateJobSpy; - - // Setup DiskWatermarkService - const isLowWatermarkSpy = sinon.stub().resolves(false); - const diskWatermarkService: any = { - isLowWatermark: isLowWatermarkSpy, - }; - - const updateWorker = new UpdateWorker( - esQueue as Esqueue, - log, - esClient as EsClient, - { - disk: { - thresholdEnabled: true, - watermarkLow: '80%', - }, - } as ServerOptions, - {} as GitOperations, - (repoServiceFactory as any) as RepositoryServiceFactory, - cancellationService as CancellationSerivce, - diskWatermarkService as DiskWatermarkService - ); - - await updateWorker.executeJob({ - payload: { - uri: 'mockrepo', - }, - options: {}, - timestamp: 0, - }); - - expect(isLowWatermarkSpy.calledOnce).toBeTruthy(); - expect(newInstanceSpy.calledOnce).toBeTruthy(); - expect(updateSpy.calledOnce).toBeTruthy(); -}); - -test('On update job completed because of cancellation ', async () => { - // Setup RepositoryService - const updateSpy = sinon.spy(); - - // Setup CancellationService - const cancelUpdateJobSpy = sinon.spy(); - const registerCancelableUpdateJobSpy = sinon.spy(); - const cancellationService: any = { - cancelUpdateJob: emptyAsyncFunc, - registerCancelableUpdateJob: emptyAsyncFunc, - }; - cancellationService.cancelUpdateJob = cancelUpdateJobSpy; - cancellationService.registerCancelableUpdateJob = registerCancelableUpdateJobSpy; - - const updateWorker = new UpdateWorker( - esQueue as Esqueue, - log, - esClient as EsClient, - { - disk: { - thresholdEnabled: true, - watermarkLow: '80%', - }, - } as ServerOptions, - {} as GitOperations, - {} as RepositoryServiceFactory, - cancellationService as CancellationSerivce, - {} as DiskWatermarkService - ); - - await updateWorker.onJobCompleted( - { - payload: { - uri: 'mockrepo', - }, - options: {}, - timestamp: 0, - }, - { - uri: 'github.com/Microsoft/TypeScript-Node-Starter', - repo: ({ - uri: 'github.com/Microsoft/TypeScript-Node-Starter', - } as any) as Repository, - // Update job is done because of cancellation. - cancelled: true, - cancelledReason: CancellationReason.REPOSITORY_DELETE, - } - ); - - // The elasticsearch update won't be called for the sake of - // cancellation. - expect(updateSpy.notCalled).toBeTruthy(); -}); - -test('Execute update job failed because of low available disk space', async () => { - // Setup RepositoryService - const updateSpy = sinon.spy(); - const repoService = { - update: emptyAsyncFunc, - }; - repoService.update = updateSpy; - const repoServiceFactory = { - newInstance: (): void => { - return; - }, - }; - const newInstanceSpy = sinon.fake.returns(repoService); - repoServiceFactory.newInstance = newInstanceSpy; - - // Setup CancellationService - const cancelUpdateJobSpy = sinon.spy(); - const registerCancelableUpdateJobSpy = sinon.spy(); - const cancellationService: any = { - cancelUpdateJob: emptyAsyncFunc, - registerCancelableUpdateJob: emptyAsyncFunc, - }; - cancellationService.cancelUpdateJob = cancelUpdateJobSpy; - cancellationService.registerCancelableUpdateJob = registerCancelableUpdateJobSpy; - - // Setup DiskWatermarkService - const isLowWatermarkSpy = sinon.stub().resolves(true); - const diskWatermarkService: any = { - isLowWatermark: isLowWatermarkSpy, - diskWatermarkViolationMessage: sinon.stub().returns('No enough disk space'), - }; - - const updateWorker = new UpdateWorker( - esQueue as Esqueue, - log, - esClient as EsClient, - { - disk: { - thresholdEnabled: true, - watermarkLow: '80%', - }, - } as ServerOptions, - {} as GitOperations, - {} as RepositoryServiceFactory, - cancellationService as CancellationSerivce, - diskWatermarkService as DiskWatermarkService - ); - - let res: UpdateWorkerResult = { - uri: 'mockrepo', - branch: 'mockbranch', - revision: 'mockrevision', - }; - try { - res = (await updateWorker.executeJob({ - payload: { - uri: 'mockrepo', - }, - options: {}, - timestamp: 0, - })) as UpdateWorkerResult; - // This step should not be touched. - expect(false).toBeTruthy(); - } catch (error) { - // Exception should be thrown. - expect(isLowWatermarkSpy.calledOnce).toBeTruthy(); - expect(newInstanceSpy.notCalled).toBeTruthy(); - expect(updateSpy.notCalled).toBeTruthy(); - } - - expect(res.cancelled).toBeTruthy(); - expect(res.cancelledReason).toEqual(CancellationReason.LOW_DISK_SPACE); - - const onJobExecutionErrorSpy = sinon.spy(); - updateWorker.onJobExecutionError = onJobExecutionErrorSpy; - - await updateWorker.onJobCompleted( - { - payload: { - uri: 'mockrepo', - }, - options: {}, - timestamp: 0, - }, - res - ); - - expect(onJobExecutionErrorSpy.calledOnce).toBeTruthy(); - // Non of the follow up steps of a normal complete job should not be called - // because the job is going to be forwarded as execution error. - expect(updateSpy.notCalled).toBeTruthy(); -}); - -test('On update job error or timeout will not persist as error', async () => { - // Setup EsClient - const esUpdateSpy = sinon.spy(); - esClient.update = esUpdateSpy; - - // Setup CancellationService - const cancelUpdateJobSpy = sinon.spy(); - const registerCancelableUpdateJobSpy = sinon.spy(); - const cancellationService: any = { - cancelUpdateJob: emptyAsyncFunc, - registerCancelableUpdateJob: emptyAsyncFunc, - }; - cancellationService.cancelUpdateJob = cancelUpdateJobSpy; - cancellationService.registerCancelableUpdateJob = registerCancelableUpdateJobSpy; - - const updateWorker = new UpdateWorker( - esQueue as Esqueue, - log, - esClient as EsClient, - { - disk: { - thresholdEnabled: true, - watermarkLow: '80%', - }, - } as ServerOptions, - {} as GitOperations, - {} as RepositoryServiceFactory, - cancellationService as CancellationSerivce, - {} as DiskWatermarkService - ); - - await updateWorker.onJobExecutionError({ - job: { - payload: { - uri: 'mockrepo', - }, - options: {}, - timestamp: 0, - }, - error: 'mock error message', - }); - - // The elasticsearch update will be called and the progress should be 'Completed' - expect(esUpdateSpy.calledOnce).toBeTruthy(); - const updateBody = JSON.parse(esUpdateSpy.getCall(0).args[0].body); - expect(updateBody.doc.repository_git_status.progress).toBe(100); -}); diff --git a/x-pack/legacy/plugins/code/server/queue/update_worker.ts b/x-pack/legacy/plugins/code/server/queue/update_worker.ts deleted file mode 100644 index dcfcba2d6f3b0..0000000000000 --- a/x-pack/legacy/plugins/code/server/queue/update_worker.ts +++ /dev/null @@ -1,121 +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 { CloneWorkerResult, Repository, WorkerReservedProgress } from '../../model'; -import { EsClient, Esqueue } from '../lib/esqueue'; -import { DiskWatermarkService } from '../disk_watermark'; -import { GitOperations } from '../git_operations'; -import { Logger } from '../log'; -import { RepositoryServiceFactory } from '../repository_service_factory'; -import { ServerOptions } from '../server_options'; -import { AbstractGitWorker } from './abstract_git_worker'; -import { CancellationReason, CancellationSerivce } from './cancellation_service'; -import { Job } from './job'; - -export class UpdateWorker extends AbstractGitWorker { - public id: string = 'update'; - - constructor( - protected readonly queue: Esqueue, - protected readonly log: Logger, - protected readonly client: EsClient, - protected readonly serverOptions: ServerOptions, - protected readonly gitOps: GitOperations, - protected readonly repoServiceFactory: RepositoryServiceFactory, - private readonly cancellationService: CancellationSerivce, - protected readonly watermarkService: DiskWatermarkService - ) { - super(queue, log, client, serverOptions, gitOps, watermarkService); - } - - public async executeJob(job: Job) { - const superRes = await super.executeJob(job); - if (superRes.cancelled) { - return superRes; - } - - const { payload, cancellationToken } = job; - const repo: Repository = payload; - this.log.info(`Execute update job for ${repo.uri}`); - const repoService = this.repoServiceFactory.newInstance( - this.serverOptions.repoPath, - this.serverOptions.credsPath, - this.log - ); - - // Try to cancel any existing update job for this repository. - await this.cancellationService.cancelUpdateJob(repo.uri, CancellationReason.NEW_JOB_OVERRIDEN); - - let cancelled = false; - let cancelledReason; - if (cancellationToken) { - cancellationToken.on((reason: string) => { - cancelled = true; - cancelledReason = reason; - }); - } - - const updateJobPromise = repoService.update(repo, async () => { - if (cancelled) { - // return false to stop the update progress - return false; - } - - // Keep an eye on the disk usage during update in case it goes above the - // disk watermark config. - if (await this.watermarkService.isLowWatermark()) { - // Cancel this update job - if (cancellationToken) { - cancellationToken.cancel(CancellationReason.LOW_DISK_SPACE); - } - // return false to stop the update progress - return false; - } - - return true; - }); - - if (cancellationToken) { - await this.cancellationService.registerCancelableUpdateJob( - repo.uri, - cancellationToken, - updateJobPromise - ); - } - const res = await updateJobPromise; - return { - ...res, - cancelled, - cancelledReason, - }; - } - - public async onJobCompleted(job: Job, res: CloneWorkerResult) { - if (res.cancelled) { - await this.onJobCancelled(job, res.cancelledReason); - // Skip updating job progress if the job is done because of cancellation. - return; - } - - this.log.info(`Update job done for ${job.payload.uri}`); - return await super.onJobCompleted(job, res); - } - - public async onJobExecutionError(res: any) { - return await this.overrideUpdateErrorProgress(res); - } - - public async onJobTimeOut(res: any) { - return await this.overrideUpdateErrorProgress(res); - } - - private async overrideUpdateErrorProgress(res: any) { - this.log.warn(`Update job error`); - this.log.warn(res.error); - // Do not persist update errors assuming the next update trial is scheduling soon. - return await this.updateProgress(res.job, WorkerReservedProgress.COMPLETED); - } -} diff --git a/x-pack/legacy/plugins/code/server/queue/worker.ts b/x-pack/legacy/plugins/code/server/queue/worker.ts deleted file mode 100644 index a3ecff215eece..0000000000000 --- a/x-pack/legacy/plugins/code/server/queue/worker.ts +++ /dev/null @@ -1,20 +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 { Job } from './job'; - -export interface Worker { - createJob(payload: any, options: any): Promise; - executeJob(job: Job): void; - enqueueJob(payload: any, options: any): void; - - onJobEnqueued(res: any): void; - onJobCompleted(job: Job, res: any): void; - onJobExecutionError(res: any): void; - onJobTimeOut(res: any): void; - - updateProgress(job: Job, progress: number): void; -} diff --git a/x-pack/legacy/plugins/code/server/repository_config_controller.ts b/x-pack/legacy/plugins/code/server/repository_config_controller.ts deleted file mode 100644 index b420a9445198e..0000000000000 --- a/x-pack/legacy/plugins/code/server/repository_config_controller.ts +++ /dev/null @@ -1,47 +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 { parseLspUrl } from '../common/uri_util'; -import { RepositoryConfig } from '../model'; -import { EsClient } from '../server/lib/esqueue'; -import { RepositoryObjectClient } from './search'; - -export class RepositoryConfigController { - private repositoryConfigCache: { [repoUri: string]: RepositoryConfig } = {}; - private repoObjectClient: RepositoryObjectClient; - - constructor(public readonly esClient: EsClient) { - this.repoObjectClient = new RepositoryObjectClient(esClient); - } - - public async isLanguageDisabled(uri: string, lang: string): Promise { - const { repoUri } = parseLspUrl(uri)!; - let repoConfig = this.repositoryConfigCache[repoUri]; - - if (!repoConfig) { - try { - repoConfig = await this.repoObjectClient.getRepositoryConfig(repoUri); - } catch (err) { - return false; - } - } - - if (lang === 'go' && repoConfig.disableGo === true) { - return true; - } - if (lang === 'java' && repoConfig.disableJava === true) { - return true; - } - if (lang === 'typescript' && repoConfig.disableTypescript === true) { - return true; - } - return false; - } - - public async resetConfigCache(repoUri: string) { - delete this.repositoryConfigCache[repoUri]; - } -} diff --git a/x-pack/legacy/plugins/code/server/repository_service.ts b/x-pack/legacy/plugins/code/server/repository_service.ts deleted file mode 100644 index d1bdbe98e87aa..0000000000000 --- a/x-pack/legacy/plugins/code/server/repository_service.ts +++ /dev/null @@ -1,202 +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 { Progress } from '@elastic/simple-git/dist'; -import del from 'del'; -import fs from 'fs'; -import { promisify } from 'util'; -import moment from 'moment'; -import path from 'path'; - -import { SimpleGit } from '@elastic/simple-git/dist/promise'; -import { RepositoryUtils } from '../common/repository_utils'; -import { CloneProgress, CloneWorkerResult, DeleteWorkerResult, Repository } from '../model'; -import { Logger } from './log'; -import { GitOperations } from './git_operations'; - -// Return false to stop the clone progress. Return true to keep going; -export type CloneProgressHandler = ( - progress: number, - cloneProgress?: CloneProgress -) => Promise; -export type UpdateProgressHandler = () => Promise; - -const GIT_FETCH_PROGRESS_CANCEL = -1; -// TODO: Cannot directly access Git.Error.CODE.EUSER (-7). Investigate why. -const NODEGIT_CALLBACK_RETURN_VALUE_ERROR = -7; -const GIT_INDEXER_PROGRESS_CALLBACK_RETURN_VALUE_ERROR_MSG = `indexer progress callback returned ${GIT_FETCH_PROGRESS_CANCEL}`; -const SSH_AUTH_ERROR = new Error('Failed to authenticate SSH session'); - -const mkdirAsync = promisify(fs.mkdir); - -function isCancelled(error: any) { - return ( - error && - (error.message.includes(GIT_INDEXER_PROGRESS_CALLBACK_RETURN_VALUE_ERROR_MSG) || - error.errno === NODEGIT_CALLBACK_RETURN_VALUE_ERROR) - ); -} - -// This is the service for any kind of repository handling, e.g. clone, update, delete, etc. -export class RepositoryService { - private readonly gitOps: GitOperations; - constructor( - private readonly repoVolPath: string, - private readonly credsPath: string, - private readonly log: Logger - ) { - this.gitOps = new GitOperations(repoVolPath); - } - - public async clone(repo: Repository, handler?: CloneProgressHandler): Promise { - if (!repo) { - throw new Error(`Invalid repository.`); - } else { - const localPath = RepositoryUtils.repositoryLocalPath(this.repoVolPath, repo.uri); - if (fs.existsSync(localPath)) { - this.log.info(`Repository exist in local path. Do update instead of clone.`); - try { - // Do update instead of clone if the local repo exists. - return await this.update(repo); - } catch (error) { - // If failed to update the current git repo living in the disk, clean up the local git repo and - // move on with the clone. - await this.remove(repo.uri); - } - } else { - await mkdirAsync(localPath, { recursive: true }); - } - // Go head with the actual clone. - const git = await this.gitOps.openGit(repo.uri, false); - await git.init(true); - await git.addRemote('origin', repo.url); - if (repo.protocol === 'ssh') { - return this.tryWithKeys(git, () => this.doFetch(git, repo, handler)); - } else { - return await this.doFetch(git, repo, handler); - } - } - } - - public async remove(uri: string): Promise { - const localPath = RepositoryUtils.repositoryLocalPath(this.repoVolPath, uri); - try { - if (localPath.split(path.sep).includes('..')) { - throw new Error('Repository path should not contain "..".'); - } - // For now, just `rm -rf` - await del([localPath], { force: true }); - this.log.info(`Remove local repository ${uri} done.`); - return { - uri, - res: true, - }; - } catch (error) { - this.log.error(`Remove local repository ${uri} error: ${error}.`); - throw error; - } - } - public async update(repo: Repository, handler?: UpdateProgressHandler) { - const git = await this.gitOps.openGit(repo.uri); - if (repo.protocol === 'ssh') { - return await this.tryWithKeys(git, () => this.doFetch(git, repo, handler)); - } else { - return await this.doFetch(git, repo, handler); - } - } - - /** - * read credentials dir, try using each privateKey until action is successful - * @param git - * @param action - */ - private async tryWithKeys(git: SimpleGit, action: () => Promise): Promise { - const files = fs.existsSync(this.credsPath) - ? new Set(fs.readdirSync(this.credsPath)) - : new Set([]); - for (const f of files) { - if (f.endsWith('.pub')) { - const privateKey = f.slice(0, f.length - 4); - if (files.has(privateKey)) { - try { - this.log.debug(`try with key ${privateKey}`); - await git.addConfig( - 'core.sshCommand', - `ssh -i ${path.join(this.credsPath, privateKey)}` - ); - return await action(); - } catch (e) { - if (e !== SSH_AUTH_ERROR) { - throw e; - } - // continue to try another key - } - } - } - } - throw SSH_AUTH_ERROR; - } - - private PROGRESS_UPDATE_THROTTLING_FREQ_MS = 1000; - private async doFetch(git: SimpleGit, repo: Repository, handler?: CloneProgressHandler) { - try { - let lastProgressUpdate = moment(); - const progressCallback = async (progress: Progress) => { - const now = moment(); - if (now.diff(lastProgressUpdate) < this.PROGRESS_UPDATE_THROTTLING_FREQ_MS) { - return 0; - } - lastProgressUpdate = now; - - if (handler) { - const resumeClone = await handler(progress.percentage); - if (!resumeClone) { - return GIT_FETCH_PROGRESS_CANCEL; - } - } - }; - await git.fetch(['origin'], undefined, { - progressCallback, - }); - const currentBranchName = (await git.raw(['symbolic-ref', 'HEAD', '--short'])).trim(); - const headRevision = await git.revparse([`origin/${currentBranchName}`]); - // Update master to match origin/master - await git.raw(['update-ref', `refs/heads/${currentBranchName}`, headRevision]); - - this.log.info( - `Clone repository from ${repo.url} done with head revision ${headRevision} and default branch ${currentBranchName}` - ); - return { - uri: repo.uri, - repo: { - ...repo, - defaultBranch: currentBranchName, - revision: headRevision, - }, - }; - } catch (error) { - if (isCancelled(error)) { - // Clone job was cancelled intentionally. Do not throw this error. - this.log.info(`Clone repository job for ${repo.uri} was cancelled.`); - this.log.debug( - `Clone repository job cancellation error: ${JSON.stringify(error, null, 2)}` - ); - return { - uri: repo.uri, - repo, - cancelled: true, - }; - } else if (error.message && error.message.startsWith(SSH_AUTH_ERROR.message)) { - throw SSH_AUTH_ERROR; - } else { - const msg = `Clone repository from ${repo.url} error.`; - this.log.error(msg); - this.log.error(error); - throw new Error(error.message); - } - } - } -} diff --git a/x-pack/legacy/plugins/code/server/repository_service_factory.ts b/x-pack/legacy/plugins/code/server/repository_service_factory.ts deleted file mode 100644 index a258f54c117c9..0000000000000 --- a/x-pack/legacy/plugins/code/server/repository_service_factory.ts +++ /dev/null @@ -1,14 +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 { Logger } from './log'; -import { RepositoryService } from './repository_service'; - -export class RepositoryServiceFactory { - public newInstance(repoPath: string, credsPath: string, log: Logger): RepositoryService { - return new RepositoryService(repoPath, credsPath, log); - } -} diff --git a/x-pack/legacy/plugins/code/server/routes/check.ts b/x-pack/legacy/plugins/code/server/routes/check.ts deleted file mode 100644 index 7e585ffc34922..0000000000000 --- a/x-pack/legacy/plugins/code/server/routes/check.ts +++ /dev/null @@ -1,51 +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 { schema } from '@kbn/config-schema'; -import fetch from 'node-fetch'; - -import { - IRouter, - KibanaRequest, - KibanaResponseFactory, - RequestHandlerContext, -} from 'src/core/server'; -import { Logger } from '../log'; - -export async function checkCodeNode(url: string, log: Logger, rndStr: string) { - try { - const res = await fetch(`${url}/api/code/codeNode?rndStr=${rndStr}`, {}); - if (res.ok) { - return await res.json(); - } - } catch (e) { - // request failed - log.error(e); - } - - log.info(`Access code node ${url} failed, try again later.`); - return null; -} - -export function checkRoute(router: IRouter, rndStr: string) { - router.get( - { - path: '/api/code/codeNode', - validate: { - query: schema.object({}, { allowUnknowns: true }), - }, - options: { - authRequired: false, - }, - }, - (context: RequestHandlerContext, req: KibanaRequest, res: KibanaResponseFactory) => { - return res.ok({ - // @ts-ignore - body: { me: req.query.rndStr === rndStr }, - }); - } - ); -} diff --git a/x-pack/legacy/plugins/code/server/routes/file.ts b/x-pack/legacy/plugins/code/server/routes/file.ts deleted file mode 100644 index 47cc16f7a6574..0000000000000 --- a/x-pack/legacy/plugins/code/server/routes/file.ts +++ /dev/null @@ -1,331 +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 { KibanaRequest, KibanaResponseFactory, RequestHandlerContext } from 'src/core/server'; -import { DEFAULT_TREE_CHILDREN_LIMIT } from '../git_operations'; -import { CodeServerRouter } from '../security'; -import { RepositoryObjectClient } from '../search'; -import { EsClientWithRequest } from '../utils/esclient_with_request'; -import { decodeRevisionString } from '../../common/uri_util'; -import { CodeServices } from '../distributed/code_services'; -import { GitServiceDefinition } from '../distributed/apis'; -import { getReferenceHelper } from '../utils/repository_reference_helper'; - -export function fileRoute(router: CodeServerRouter, codeServices: CodeServices) { - const gitService = codeServices.serviceFor(GitServiceDefinition); - - async function getRepoUriFromMeta( - context: RequestHandlerContext, - req: KibanaRequest, - repoUri: string - ): Promise { - const repoObjectClient = new RepositoryObjectClient(new EsClientWithRequest(context, req)); - - try { - const repo = await repoObjectClient.getRepository(repoUri); - await getReferenceHelper(context.core.savedObjects.client).ensureReference(repo.uri); - return repo.uri; - } catch (e) { - return undefined; - } - } - - router.route({ - path: '/api/code/repo/{uri*3}/tree/{ref}/{path*}', - method: 'GET', - async npHandler( - context: RequestHandlerContext, - req: KibanaRequest, - res: KibanaResponseFactory - ) { - const { uri, path, ref } = req.params as any; - const revision = decodeRevisionString(ref); - const queries = req.query as any; - const limit = queries.limit - ? parseInt(queries.limit as string, 10) - : DEFAULT_TREE_CHILDREN_LIMIT; - const skip = queries.skip ? parseInt(queries.skip as string, 10) : 0; - const withParents = 'parents' in queries; - const flatten = 'flatten' in queries; - const repoUri = await getRepoUriFromMeta(context, req, uri); - if (!repoUri) { - return res.notFound({ body: `repo ${uri} not found` }); - } - const endpoint = await codeServices.locate(req, uri); - try { - const filetree = await gitService.fileTree(endpoint, { - uri: repoUri, - path, - revision, - skip, - limit, - withParents, - flatten, - }); - return res.ok({ body: filetree }); - } catch (e) { - if (e.isBoom) { - return res.customError({ - body: e.error, - statusCode: e.statusCode ? e.statusCode : 500, - }); - } else { - return res.internalError({ body: e.message || e.name }); - } - } - }, - }); - - router.route({ - path: '/api/code/repo/{uri*3}/blob/{ref}/{path*}', - method: 'GET', - async npHandler( - context: RequestHandlerContext, - req: KibanaRequest, - res: KibanaResponseFactory - ) { - const { uri, path, ref } = req.params as any; - const revision = decodeRevisionString(ref); - const repoUri = await getRepoUriFromMeta(context, req, uri); - if (!repoUri) { - return res.notFound({ body: `repo ${uri} not found` }); - } - const endpoint = await codeServices.locate(req, uri); - try { - const blob = await gitService.blob(endpoint, { - uri, - path, - line: (req.query as any).line as string, - revision: decodeURIComponent(revision), - }); - - if (blob.imageType) { - return res.ok({ - body: blob.content, - headers: { 'Content-Type': blob.imageType }, - }); - } else if (blob.isBinary) { - return res.noContent({ - headers: { 'Content-Type': 'application/octet-stream' }, - }); - } else { - if (blob.content) { - return res.ok({ - body: blob.content, - headers: { - 'Content-Type': 'text/plain', - lang: blob.lang!, - }, - }); - } else { - return res.ok({ - body: blob.content, - headers: { 'Content-Type': 'text/big' }, - }); - } - } - } catch (e) { - if (e.isBoom) { - return res.customError({ - body: e.error, - statusCode: e.statusCode ? e.statusCode : 500, - }); - } else { - return res.internalError({ body: e.message || e.name }); - } - } - }, - }); - - router.route({ - path: '/app/code/repo/{uri*3}/raw/{ref}/{path*}', - method: 'GET', - async npHandler( - context: RequestHandlerContext, - req: KibanaRequest, - res: KibanaResponseFactory - ) { - const { uri, path, ref } = req.params as any; - const revision = decodeRevisionString(ref); - const repoUri = await getRepoUriFromMeta(context, req, uri); - if (!repoUri) { - return res.notFound({ body: `repo ${uri} not found` }); - } - const endpoint = await codeServices.locate(req, uri); - - try { - const blob = await gitService.raw(endpoint, { uri: repoUri, path, revision }); - if (blob.isBinary) { - return res.ok({ - body: blob.content, - headers: { 'Content-Transfer-Encoding': 'binary' }, - }); - } else { - return res.ok({ - body: blob.content, - headers: { 'Content-Type': 'text/plain' }, - }); - } - } catch (e) { - if (e.isBoom) { - return res.customError({ - body: e.error, - statusCode: e.statusCode ? e.statusCode : 500, - }); - } else { - return res.internalError({ body: e.message || e.name }); - } - } - }, - }); - - router.route({ - path: '/api/code/repo/{uri*3}/history/{ref}', - method: 'GET', - npHandler: historyHandler, - }); - - router.route({ - path: '/api/code/repo/{uri*3}/history/{ref}/{path*}', - method: 'GET', - npHandler: historyHandler, - }); - - async function historyHandler( - context: RequestHandlerContext, - req: KibanaRequest, - res: KibanaResponseFactory - ) { - const { uri, ref, path } = req.params as any; - const revision = decodeRevisionString(ref); - const queries = req.query as any; - const count = queries.count ? parseInt(queries.count as string, 10) : 10; - const after = queries.after !== undefined; - try { - const repoUri = await getRepoUriFromMeta(context, req, uri); - if (!repoUri) { - return res.notFound({ body: `repo ${uri} not found` }); - } - const endpoint = await codeServices.locate(req, uri); - const history = await gitService.history(endpoint, { - uri: repoUri, - path, - revision, - count, - after, - }); - return res.ok({ body: history }); - } catch (e) { - if (e.isBoom) { - return res.customError({ - body: e.error, - statusCode: e.statusCode ? e.statusCode : 500, - }); - } else { - return res.internalError({ body: e.message || e.name }); - } - } - } - - router.route({ - path: '/api/code/repo/{uri*3}/references', - method: 'GET', - async npHandler( - context: RequestHandlerContext, - req: KibanaRequest, - res: KibanaResponseFactory - ) { - const { uri } = req.params as any; - const repoUri = await getRepoUriFromMeta(context, req, uri); - if (!repoUri) { - return res.badRequest({ body: `repo ${uri} not found` }); - } - const endpoint = await codeServices.locate(req, uri); - - try { - const branchesAndTags = await gitService.branchesAndTags(endpoint, { uri: repoUri }); - return res.ok({ body: branchesAndTags }); - } catch (e) { - if (e.isBoom) { - return res.customError({ - body: e.error, - statusCode: e.statusCode ? e.statusCode : 500, - }); - } else { - return res.internalError({ body: e.message || e.name }); - } - } - }, - }); - - router.route({ - path: '/api/code/repo/{uri*3}/diff/{revision}', - method: 'GET', - async npHandler( - context: RequestHandlerContext, - req: KibanaRequest, - res: KibanaResponseFactory - ) { - const { uri, revision } = req.params as any; - const repoUri = await getRepoUriFromMeta(context, req, uri); - if (!repoUri) { - return res.notFound({ body: `repo ${uri} not found` }); - } - const endpoint = await codeServices.locate(req, uri); - try { - const diff = await gitService.commitDiff(endpoint, { - uri: repoUri, - revision: decodeRevisionString(revision), - }); - return res.ok({ body: diff }); - } catch (e) { - if (e.isBoom) { - return res.customError({ - body: e.error, - statusCode: e.statusCode ? e.statusCode : 500, - }); - } else { - return res.internalError({ body: e.message || e.name }); - } - } - }, - }); - - router.route({ - path: '/api/code/repo/{uri*3}/blame/{revision}/{path*}', - method: 'GET', - async npHandler( - context: RequestHandlerContext, - req: KibanaRequest, - res: KibanaResponseFactory - ) { - const { uri, path, revision } = req.params as any; - const repoUri = await getRepoUriFromMeta(context, req, uri); - if (!repoUri) { - return res.notFound({ body: `repo ${uri} not found` }); - } - const endpoint = await codeServices.locate(req, uri); - - try { - const blames = await gitService.blame(endpoint, { - uri: repoUri, - revision: decodeRevisionString(decodeURIComponent(revision)), - path, - }); - return res.ok({ body: blames }); - } catch (e) { - if (e.isBoom) { - return res.customError({ - body: e.error, - statusCode: e.statusCode ? e.statusCode : 500, - }); - } else { - return res.internalError({ body: e.message || e.name }); - } - } - }, - }); -} diff --git a/x-pack/legacy/plugins/code/server/routes/index.ts b/x-pack/legacy/plugins/code/server/routes/index.ts deleted file mode 100644 index 82973ac1d2791..0000000000000 --- a/x-pack/legacy/plugins/code/server/routes/index.ts +++ /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 * from './check'; -export * from './file'; -export * from './install'; -export * from './lsp'; -export * from './repository'; -export * from './search'; -export * from './setup'; -export * from './status'; -export * from './workspace'; diff --git a/x-pack/legacy/plugins/code/server/routes/install.ts b/x-pack/legacy/plugins/code/server/routes/install.ts deleted file mode 100644 index 28ccc4012ceec..0000000000000 --- a/x-pack/legacy/plugins/code/server/routes/install.ts +++ /dev/null @@ -1,71 +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 { KibanaRequest, KibanaResponseFactory, RequestHandlerContext } from 'src/core/server'; -import { ServerFacade } from '../..'; -import { enabledLanguageServers, LanguageServerDefinition } from '../lsp/language_servers'; -import { CodeServerRouter } from '../security'; -import { CodeServices } from '../distributed/code_services'; -import { LspServiceDefinition } from '../distributed/apis'; -import { Endpoint } from '../distributed/resource_locator'; -import { ServerOptions } from '../server_options'; - -export function installRoute( - server: ServerFacade, - router: CodeServerRouter, - codeServices: CodeServices, - options: ServerOptions -) { - const lspService = codeServices.serviceFor(LspServiceDefinition); - const kibanaVersion = server.config().get('pkg.version') as string; - const status = async (endpoint: Endpoint, def: LanguageServerDefinition) => ({ - name: def.name, - status: await lspService.languageServerStatus(endpoint, { langName: def.name }), - version: def.version, - build: def.build, - languages: def.languages, - installationType: def.installationType, - downloadUrl: - typeof def.downloadUrl === 'function' ? def.downloadUrl(kibanaVersion) : def.downloadUrl, - pluginName: def.installationPluginName, - }); - - router.route({ - path: '/api/code/install', - async npHandler( - context: RequestHandlerContext, - req: KibanaRequest, - res: KibanaResponseFactory - ) { - const endpoint = await codeServices.locate(req, ''); - const installRes = await Promise.all( - enabledLanguageServers(options).map(def => status(endpoint, def)) - ); - return res.ok({ body: installRes }); - }, - method: 'GET', - }); - - router.route({ - path: '/api/code/install/{name}', - async npHandler( - context: RequestHandlerContext, - req: KibanaRequest, - res: KibanaResponseFactory - ) { - const { name } = req.params as any; - const def = enabledLanguageServers(options).find(d => d.name === name); - const endpoint = await codeServices.locate(req, ''); - if (def) { - const installRes = await status(endpoint, def); - return res.ok({ body: installRes }); - } else { - return res.notFound({ body: `language server ${name} not found.` }); - } - }, - method: 'GET', - }); -} diff --git a/x-pack/legacy/plugins/code/server/routes/lsp.ts b/x-pack/legacy/plugins/code/server/routes/lsp.ts deleted file mode 100644 index 6b8af10f9f11e..0000000000000 --- a/x-pack/legacy/plugins/code/server/routes/lsp.ts +++ /dev/null @@ -1,239 +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 { ResponseError } from 'vscode-jsonrpc'; -import { ResponseMessage } from 'vscode-jsonrpc/lib/messages'; -import { SymbolLocator } from '@elastic/lsp-extension'; -import { KibanaRequest, KibanaResponseFactory, RequestHandlerContext } from 'src/core/server'; - -import { - LanguageServerStartFailed, - ServerNotInitialized, - UnknownFileLanguage, -} from '../../common/lsp_error_codes'; -import { parseLspUrl } from '../../common/uri_util'; -import { Logger } from '../log'; -import { SymbolSearchClient } from '../search'; -import { CodeServerRouter } from '../security'; -import { ServerOptions } from '../server_options'; - -import { EsClientWithRequest } from '../utils/esclient_with_request'; -import { promiseTimeout } from '../utils/timeout'; -import { CodeServices } from '../distributed/code_services'; -import { GitServiceDefinition, LspServiceDefinition } from '../distributed/apis'; -import { findTitleFromHover, groupFiles } from '../utils/lsp_utils'; -import { getReferenceHelper } from '../utils/repository_reference_helper'; -import { SymbolSearchResult } from '../../model'; - -const LANG_SERVER_ERROR = 'language server error'; - -export function lspRoute( - router: CodeServerRouter, - codeServices: CodeServices, - serverOptions: ServerOptions, - log: Logger -) { - const lspService = codeServices.serviceFor(LspServiceDefinition); - const gitService = codeServices.serviceFor(GitServiceDefinition); - - router.route({ - path: '/api/code/lsp/textDocument/{method}', - async npHandler( - context: RequestHandlerContext, - req: KibanaRequest, - res: KibanaResponseFactory - ) { - if (typeof req.body === 'object' && req.body != null) { - // @ts-ignore - const method = req.params.method; - if (method) { - try { - const params = (req.body as unknown) as any; - const uri = params.textDocument.uri; - const { repoUri } = parseLspUrl(uri)!; - await getReferenceHelper(context.core.savedObjects.client).ensureReference(repoUri); - const endpoint = await codeServices.locate(req, repoUri); - const requestPromise = lspService.sendRequest(endpoint, { - method: `textDocument/${method}`, - params: req.body, - }); - const result = await promiseTimeout(serverOptions.lsp.requestTimeoutMs, requestPromise); - return res.ok({ body: result }); - } catch (error) { - if (error instanceof ResponseError) { - // hide some errors; - if ( - error.code === UnknownFileLanguage || - error.code === ServerNotInitialized || - error.code === LanguageServerStartFailed - ) { - log.debug(error); - } - return res.custom({ - statusCode: 500, - body: { error: { code: 500, msg: LANG_SERVER_ERROR } }, - }); - } else if (error.isBoom) { - return res.customError({ - body: error.error, - statusCode: error.statusCode ? error.statusCode : 500, - }); - } else { - log.error(error); - return res.custom({ - statusCode: 500, - body: { error: { code: 500, msg: LANG_SERVER_ERROR } }, - }); - } - } - } else { - return res.badRequest({ body: 'missing `method` in request' }); - } - } else { - return res.badRequest({ body: 'json body required' }); - } - }, - method: 'POST', - }); - - router.route({ - path: '/api/code/lsp/findDefinitions', - method: 'POST', - async npHandler( - context: RequestHandlerContext, - req: KibanaRequest, - res: KibanaResponseFactory - ) { - // @ts-ignore - const { textDocument, position } = req.body as any; - // @ts-ignore - const { qname } = req.params as any; - const { uri } = textDocument; - const { repoUri } = parseLspUrl(uri); - await getReferenceHelper(context.core.savedObjects.client).ensureReference(repoUri); - const endpoint = await codeServices.locate(req, repoUri); - const response: ResponseMessage = await promiseTimeout( - serverOptions.lsp.requestTimeoutMs, - lspService.sendRequest(endpoint, { - method: `textDocument/edefinition`, - params: { textDocument: { uri }, position }, - }) - ); - const hover = await lspService.sendRequest(endpoint, { - method: 'textDocument/hover', - params: { - textDocument: { uri }, - position, - }, - }); - const title: string = await findTitleFromHover(hover, uri, position); - const symbolSearchClient = new SymbolSearchClient(new EsClientWithRequest(context, req), log); - - const locators = response.result as SymbolLocator[]; - const locations = []; - const repoScope = await getReferenceHelper(context.core.savedObjects.client).findReferences(); - for (const locator of locators) { - if (locator.location) { - locations.push(locator.location); - } else if (locator.qname && repoScope.length > 0) { - const searchResults = await symbolSearchClient.findByQname(qname, repoScope); - for (const symbol of searchResults.symbols) { - locations.push(symbol.symbolInformation.location); - } - } - } - const files = await groupFiles(locations, async loc => { - const ep = await codeServices.locate(req, loc.uri); - return await gitService.blob(ep, loc); - }); - return res.ok({ body: { title, files, uri, position } }); - }, - }); - - router.route({ - path: '/api/code/lsp/findReferences', - method: 'POST', - async npHandler( - context: RequestHandlerContext, - req: KibanaRequest, - res: KibanaResponseFactory - ) { - try { - const { textDocument, position } = req.body as any; - const { uri } = textDocument; - const { repoUri } = parseLspUrl(uri); - await getReferenceHelper(context.core.savedObjects.client).ensureReference(repoUri); - const endpoint = await codeServices.locate(req, repoUri); - const response: ResponseMessage = await promiseTimeout( - serverOptions.lsp.requestTimeoutMs, - lspService.sendRequest(endpoint, { - method: `textDocument/references`, - params: { textDocument: { uri }, position }, - }) - ); - const hover = await lspService.sendRequest(endpoint, { - method: 'textDocument/hover', - params: { - textDocument: { uri }, - position, - }, - }); - const title: string = await findTitleFromHover(hover, uri, position); - const files = await groupFiles(response.result, async loc => { - const ep = await codeServices.locate(req, loc.uri); - return await gitService.blob(ep, loc); - }); - return res.ok({ body: { title, files, uri, position } }); - } catch (error) { - log.error(error); - if (error instanceof ResponseError) { - return res.custom({ - statusCode: 500, - body: { error: { code: error.code, msg: LANG_SERVER_ERROR } }, - }); - } else if (error.isBoom) { - return res.customError({ - body: error.error, - statusCode: error.statusCode ? error.statusCode : 500, - }); - } else { - return res.custom({ - statusCode: 500, - body: { error: { code: 500, msg: LANG_SERVER_ERROR } }, - }); - } - } - }, - }); -} - -export function symbolByQnameRoute(router: CodeServerRouter, log: Logger) { - router.route({ - path: '/api/code/lsp/symbol/{qname}', - method: 'GET', - async npHandler( - context: RequestHandlerContext, - req: KibanaRequest, - res: KibanaResponseFactory - ) { - // @ts-ignore - const { qname } = req.params as any; - const symbolSearchClient = new SymbolSearchClient(new EsClientWithRequest(context, req), log); - const repoScope = await getReferenceHelper(context.core.savedObjects.client).findReferences(); - if (repoScope.length === 0) { - return res.ok({ - body: { - symbols: [], - total: 0, - took: 0, - } as SymbolSearchResult, - }); - } - const symbol = await symbolSearchClient.findByQname(qname, repoScope); - return res.ok({ body: symbol }); - }, - }); -} diff --git a/x-pack/legacy/plugins/code/server/routes/repository.ts b/x-pack/legacy/plugins/code/server/routes/repository.ts deleted file mode 100644 index d9e8edb4d2f50..0000000000000 --- a/x-pack/legacy/plugins/code/server/routes/repository.ts +++ /dev/null @@ -1,368 +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 { i18n } from '@kbn/i18n'; -import { KibanaRequest, KibanaResponseFactory, RequestHandlerContext } from 'src/core/server'; - -import { validateGitUrl } from '../../common/git_url_utils'; -import { RepositoryUtils } from '../../common/repository_utils'; -import { RepositoryConfig, RepositoryUri, WorkerReservedProgress } from '../../model'; -import { RepositoryIndexInitializer, RepositoryIndexInitializerFactory } from '../indexer'; -import { Logger } from '../log'; -import { RepositoryConfigController } from '../repository_config_controller'; -import { RepositoryObjectClient } from '../search'; -import { ServerOptions } from '../server_options'; -import { EsClientWithRequest } from '../utils/esclient_with_request'; -import { CodeServerRouter } from '../security'; -import { CodeServices } from '../distributed/code_services'; -import { RepositoryServiceDefinition } from '../distributed/apis'; -import { getReferenceHelper } from '../utils/repository_reference_helper'; - -export function repositoryRoute( - router: CodeServerRouter, - codeServices: CodeServices, - repoIndexInitializerFactory: RepositoryIndexInitializerFactory, - repoConfigController: RepositoryConfigController, - options: ServerOptions, - log: Logger -) { - const repositoryService = codeServices.serviceFor(RepositoryServiceDefinition); - // Clone a git repository - router.route({ - path: '/api/code/repo', - requireAdmin: true, - method: 'POST', - async npHandler( - context: RequestHandlerContext, - req: KibanaRequest, - res: KibanaResponseFactory - ) { - const repoUrl: string = (req.body as any).url; - - // Reject the request if the url is an invalid git url. - try { - validateGitUrl( - repoUrl, - options.security.gitHostWhitelist, - options.security.gitProtocolWhitelist - ); - } catch (error) { - log.error(`Validate git url ${repoUrl} error.`); - log.error(error); - return res.badRequest({ body: error }); - } - - const repo = RepositoryUtils.buildRepository(repoUrl); - const repoObjectClient = new RepositoryObjectClient(new EsClientWithRequest(context, req)); - - try { - // Check if the repository already exists - await repoObjectClient.getRepository(repo.uri); - // distinguish between that the repository exists in the current space and that the repository exists in - // another space, and return the default message if error happens during reference checking. - try { - const hasRef = await getReferenceHelper(context.core.savedObjects.client).hasReference( - repo.uri - ); - if (!hasRef) { - return res.custom({ - statusCode: 409, // conflict - body: i18n.translate( - 'xpack.code.repositoryManagement.repoOtherSpaceImportedMessage', - { - defaultMessage: 'The repository has already been imported in another space!', - } - ), - }); - } - } catch (e) { - log.error(`Failed to check reference for ${repo.uri} in current space`); - } - const msg = `Repository ${repoUrl} already exists. Skip clone.`; - log.info(msg); - return res.custom({ statusCode: 304, body: msg }); - } catch (error) { - log.info(`Repository ${repoUrl} does not exist. Go ahead with clone.`); - try { - // create the reference first, and make the creation idempotent, to avoid potential dangling repositories - // which have no references from any space, in case the writes to ES may fail independently - await getReferenceHelper(context.core.savedObjects.client).createReference(repo.uri); - - // Create the index for the repository - const initializer = (await repoIndexInitializerFactory.create( - repo.uri, - '' - )) as RepositoryIndexInitializer; - await initializer.init(); - - // Persist to elasticsearch - await repoObjectClient.setRepository(repo.uri, repo); - - // Kick off clone job - const payload = { - url: repoUrl, - }; - // before the repository is cloned, it doesn't exist in the cluster - // in this case, it should be allocated right away, otherwise it cannot be found in the cluster - const endpoint = await codeServices.allocate(req, repoUrl); - // the endpoint could be undefined, as we may not clone it here for the cluster mode implementation - if (endpoint) { - await repositoryService.clone(endpoint, payload); - } - return res.ok({ body: repo }); - } catch (error2) { - const msg = `Issue repository clone request for ${repoUrl} error`; - log.error(msg); - log.error(error2); - return res.badRequest({ body: msg }); - } - } - }, - }); - - // Remove a git repository - router.route({ - path: '/api/code/repo/{uri*3}', - requireAdmin: true, - method: 'DELETE', - async npHandler( - context: RequestHandlerContext, - req: KibanaRequest, - res: KibanaResponseFactory - ) { - const { uri: repoUri } = req.params as any; - const repoObjectClient = new RepositoryObjectClient(new EsClientWithRequest(context, req)); - try { - // make sure the repo belongs to the current space - getReferenceHelper(context.core.savedObjects.client).ensureReference(repoUri); - - // Check if the repository already exists. If not, an error will be thrown. - await repoObjectClient.getRepository(repoUri); - - // Check if the repository delete status already exists. If so, we should ignore this - // request. - try { - const status = await repoObjectClient.getRepositoryDeleteStatus(repoUri); - // if the delete status is an ERROR, we can give it another try - if (status.progress !== WorkerReservedProgress.ERROR) { - const msg = `Repository ${repoUri} is already in delete.`; - log.info(msg); - return res.custom({ statusCode: 304, body: msg }); - } - } catch (error) { - // Do nothing here since this error is expected. - log.info(`Repository ${repoUri} delete status does not exist. Go ahead with delete.`); - } - - const payload = { - uri: repoUri, - }; - const endpoint = await codeServices.locate(req, repoUri); - await repositoryService.delete(endpoint, payload); - // delete the reference last to avoid dangling repositories - await getReferenceHelper(context.core.savedObjects.client).deleteReference(repoUri); - return res.ok(); - } catch (error) { - const msg = `Issue repository delete request for ${repoUri} error`; - log.error(msg); - log.error(error); - return res.notFound({ body: msg }); - } - }, - }); - - // Get a git repository - router.route({ - path: '/api/code/repo/{uri*3}', - method: 'GET', - async npHandler( - context: RequestHandlerContext, - req: KibanaRequest, - res: KibanaResponseFactory - ) { - const { uri: repoUri } = req.params as any; - try { - await getReferenceHelper(context.core.savedObjects.client).ensureReference(repoUri); - const repoObjectClient = new RepositoryObjectClient(new EsClientWithRequest(context, req)); - const repo = await repoObjectClient.getRepository(repoUri); - return res.ok({ body: repo }); - } catch (error) { - const msg = `Get repository ${repoUri} error`; - log.error(msg); - log.error(error); - return res.notFound({ body: msg }); - } - }, - }); - - router.route({ - path: '/api/code/repo/status/{uri*3}', - method: 'GET', - async npHandler( - context: RequestHandlerContext, - req: KibanaRequest, - res: KibanaResponseFactory - ) { - const { uri: repoUri } = req.params as any; - try { - const repoObjectClient = new RepositoryObjectClient(new EsClientWithRequest(context, req)); - let gitStatus = null; - let indexStatus = null; - let deleteStatus = null; - const hasRef = await getReferenceHelper(context.core.savedObjects.client).hasReference( - repoUri - ); - - if (hasRef) { - try { - gitStatus = await repoObjectClient.getRepositoryGitStatus(repoUri); - } catch (error) { - log.debug(`Get repository git status ${repoUri} error: ${error}`); - } - - try { - indexStatus = await repoObjectClient.getRepositoryIndexStatus(repoUri); - } catch (error) { - log.debug(`Get repository index status ${repoUri} error: ${error}`); - } - - try { - deleteStatus = await repoObjectClient.getRepositoryDeleteStatus(repoUri); - } catch (error) { - log.debug(`Get repository delete status ${repoUri} error: ${error}`); - } - } - const status = { - gitStatus, - indexStatus, - deleteStatus, - }; - return res.ok({ body: status }); - } catch (error) { - const msg = `Get repository status ${repoUri} error`; - log.error(msg); - log.error(error); - return res.notFound({ body: msg }); - } - }, - }); - - // Get all git repositories - router.route({ - path: '/api/code/repos', - method: 'GET', - async npHandler( - context: RequestHandlerContext, - req: KibanaRequest, - res: KibanaResponseFactory - ) { - try { - const uris = await getReferenceHelper(context.core.savedObjects.client).findReferences(); - const repoObjectClient = new RepositoryObjectClient(new EsClientWithRequest(context, req)); - const repo = await repoObjectClient.getRepositories(uris); - return res.ok({ body: repo }); - } catch (error) { - const msg = `Get all repositories error`; - log.error(msg); - log.error(error); - return res.notFound({ body: msg }); - } - }, - }); - - // Issue a repository index task. - // TODO(mengwei): This is just temporary API stub to trigger the index job. Eventually in the near - // future, this route will be removed. The scheduling strategy is still in discussion. - router.route({ - path: '/api/code/repo/index/{uri*3}', - method: 'POST', - requireAdmin: true, - async npHandler( - context: RequestHandlerContext, - req: KibanaRequest, - res: KibanaResponseFactory - ) { - const { uri: repoUri } = req.params as any; - const reindex: boolean = (req.body as any).reindex; - try { - await getReferenceHelper(context.core.savedObjects.client).ensureReference(repoUri); - const repoObjectClient = new RepositoryObjectClient(new EsClientWithRequest(context, req)); - const cloneStatus = await repoObjectClient.getRepositoryGitStatus(repoUri); - - const payload = { - uri: repoUri, - revision: cloneStatus.revision, - enforceReindex: reindex, - }; - const endpoint = await codeServices.locate(req, repoUri); - await repositoryService.index(endpoint, payload); - return res.ok(); - } catch (error) { - const msg = `Index repository ${repoUri} error`; - log.error(msg); - log.error(error); - return res.notFound({ body: msg }); - } - }, - }); - - // Update a repo config - router.route({ - path: '/api/code/repo/config/{uri*3}', - method: 'PUT', - requireAdmin: true, - async npHandler( - context: RequestHandlerContext, - req: KibanaRequest, - res: KibanaResponseFactory - ) { - const config: RepositoryConfig = req.body as RepositoryConfig; - const repoUri: RepositoryUri = config.uri; - const repoObjectClient = new RepositoryObjectClient(new EsClientWithRequest(context, req)); - - try { - // Check if the repository exists - await getReferenceHelper(context.core.savedObjects.client).ensureReference(repoUri); - await repoObjectClient.getRepository(repoUri); - } catch (error) { - return res.badRequest({ body: `Repository not existed for ${repoUri}` }); - } - - try { - // Persist to elasticsearch - await repoObjectClient.setRepositoryConfig(repoUri, config); - repoConfigController.resetConfigCache(repoUri); - return res.ok(); - } catch (error) { - const msg = `Update repository config for ${repoUri} error`; - log.error(msg); - log.error(error); - return res.notFound({ body: msg }); - } - }, - }); - - // Get repository config - router.route({ - path: '/api/code/repo/config/{uri*3}', - method: 'GET', - async npHandler( - context: RequestHandlerContext, - req: KibanaRequest, - res: KibanaResponseFactory - ) { - const { uri: repoUri } = req.params as any; - try { - await getReferenceHelper(context.core.savedObjects.client).ensureReference(repoUri); - const repoObjectClient = new RepositoryObjectClient(new EsClientWithRequest(context, req)); - const config = await repoObjectClient.getRepositoryConfig(repoUri); - return res.ok({ body: config }); - } catch (error) { - return res.notFound({ body: `Repository config ${repoUri} not exist` }); - } - }, - }); -} diff --git a/x-pack/legacy/plugins/code/server/routes/search.ts b/x-pack/legacy/plugins/code/server/routes/search.ts deleted file mode 100644 index 5c2b731b33c42..0000000000000 --- a/x-pack/legacy/plugins/code/server/routes/search.ts +++ /dev/null @@ -1,294 +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 { KibanaRequest, KibanaResponseFactory, RequestHandlerContext } from 'src/core/server'; -import { - CommitSearchRequest, - DocumentSearchRequest, - RepositorySearchRequest, - ResolveSnippetsRequest, - StackTraceItem, - StackTraceSnippetsRequest, - SymbolSearchRequest, -} from '../../model'; -import { Logger } from '../log'; -import { - CommitSearchClient, - DocumentSearchClient, - IntegrationsSearchClient, - RepositorySearchClient, - SymbolSearchClient, -} from '../search'; -import { EsClientWithRequest } from '../utils/esclient_with_request'; -import { CodeServerRouter } from '../security'; -import { getReferenceHelper } from '../utils/repository_reference_helper'; - -export function repositorySearchRoute(router: CodeServerRouter, log: Logger) { - router.route({ - path: '/api/code/search/repo', - method: 'GET', - async npHandler( - context: RequestHandlerContext, - req: KibanaRequest, - res: KibanaResponseFactory - ) { - let page = 1; - const { p, q, repoScope } = req.query as any; - if (p) { - page = parseInt(p as string, 10); - } - - const searchReq: RepositorySearchRequest = { - query: q as string, - page, - repoScope: await getScope(context, repoScope), - }; - try { - const repoSearchClient = new RepositorySearchClient( - new EsClientWithRequest(context, req), - log - ); - const searchRes = await repoSearchClient.search(searchReq); - return res.ok({ body: searchRes }); - } catch (error) { - return res.internalError({ body: 'Search Exception' }); - } - }, - }); - - router.route({ - path: '/api/code/suggestions/repo', - method: 'GET', - async npHandler( - context: RequestHandlerContext, - req: KibanaRequest, - res: KibanaResponseFactory - ) { - let page = 1; - const { p, q, repoScope } = req.query as any; - if (p) { - page = parseInt(p as string, 10); - } - - const searchReq: RepositorySearchRequest = { - query: q as string, - page, - repoScope: await getScope(context, repoScope), - }; - try { - const repoSearchClient = new RepositorySearchClient( - new EsClientWithRequest(context, req), - log - ); - const searchRes = await repoSearchClient.suggest(searchReq); - return res.ok({ body: searchRes }); - } catch (error) { - return res.internalError({ body: 'Search Exception' }); - } - }, - }); -} - -export function documentSearchRoute(router: CodeServerRouter, log: Logger) { - router.route({ - path: '/api/code/search/doc', - method: 'GET', - async npHandler( - context: RequestHandlerContext, - req: KibanaRequest, - res: KibanaResponseFactory - ) { - let page = 1; - const { p, q, langs, repos, repoScope } = req.query as any; - if (p) { - page = parseInt(p as string, 10); - } - - const searchReq: DocumentSearchRequest = { - query: q as string, - page, - langFilters: langs ? (langs as string).split(',') : [], - repoFilters: repos ? decodeURIComponent(repos as string).split(',') : [], - repoScope: await getScope(context, repoScope), - }; - try { - const docSearchClient = new DocumentSearchClient( - new EsClientWithRequest(context, req), - log - ); - const searchRes = await docSearchClient.search(searchReq); - return res.ok({ body: searchRes }); - } catch (error) { - return res.internalError({ body: 'Search Exception' }); - } - }, - }); - - router.route({ - path: '/api/code/suggestions/doc', - method: 'GET', - async npHandler( - context: RequestHandlerContext, - req: KibanaRequest, - res: KibanaResponseFactory - ) { - let page = 1; - const { p, q, repoScope } = req.query as any; - if (p) { - page = parseInt(p as string, 10); - } - - const searchReq: DocumentSearchRequest = { - query: q as string, - page, - repoScope: await getScope(context, repoScope), - }; - try { - const docSearchClient = new DocumentSearchClient( - new EsClientWithRequest(context, req), - log - ); - const searchRes = await docSearchClient.suggest(searchReq); - return res.ok({ body: searchRes }); - } catch (error) { - return res.internalError({ body: 'Search Exception' }); - } - }, - }); - - // Resolve source code snippets base on APM's stacktrace item data including: - // * repoUri: ID of the repository - // * revision: Optional. Revision of the file. - // * filePath: the path of the file. - // * lineNumStart: the start line number of the snippet. - // * lineNumEnd: Optional. The end line number of the snippet. - // We can always add more context for snippet resolution in the future. - router.route({ - path: '/api/code/integration/snippets', - method: 'POST', - async npHandler( - context: RequestHandlerContext, - req: KibanaRequest, - res: KibanaResponseFactory - ) { - const scopes = new Set( - await getReferenceHelper(context.core.savedObjects.client).findReferences() - ); - const reqs: StackTraceSnippetsRequest[] = (req.body as any).requests; - const searchRes = await Promise.all( - reqs.map((stacktraceReq: StackTraceSnippetsRequest) => { - const integClient = new IntegrationsSearchClient( - new EsClientWithRequest(context, req), - log - ); - return Promise.all( - stacktraceReq.stacktraceItems.map((stacktrace: StackTraceItem) => { - const repoUris = stacktraceReq.repoUris.filter(uri => scopes.has(uri)); - const integrationReq: ResolveSnippetsRequest = { - repoUris, - revision: stacktraceReq.revision, - filePath: stacktrace.filePath, - lineNumStart: stacktrace.lineNumStart, - lineNumEnd: stacktrace.lineNumEnd, - }; - return integClient.resolveSnippets(integrationReq); - }) - ); - }) - ); - return res.ok({ body: searchRes }); - }, - }); -} - -export function symbolSearchRoute(router: CodeServerRouter, log: Logger) { - const symbolSearchHandler = async ( - context: RequestHandlerContext, - req: KibanaRequest, - res: KibanaResponseFactory - ) => { - let page = 1; - const { p, q, repoScope } = req.query as any; - if (p) { - page = parseInt(p as string, 10); - } - - const searchReq: SymbolSearchRequest = { - query: q as string, - page, - repoScope: await getScope(context, repoScope), - }; - try { - const symbolSearchClient = new SymbolSearchClient(new EsClientWithRequest(context, req), log); - const searchRes = await symbolSearchClient.suggest(searchReq); - return res.ok({ body: searchRes }); - } catch (error) { - return res.internalError({ body: 'Search Exception' }); - } - }; - - // Currently these 2 are the same. - router.route({ - path: '/api/code/suggestions/symbol', - method: 'GET', - npHandler: symbolSearchHandler, - }); - router.route({ - path: '/api/code/search/symbol', - method: 'GET', - npHandler: symbolSearchHandler, - }); -} - -export function commitSearchRoute(router: CodeServerRouter, log: Logger) { - router.route({ - path: '/api/code/search/commit', - method: 'GET', - async npHandler( - context: RequestHandlerContext, - req: KibanaRequest, - res: KibanaResponseFactory - ) { - let page = 1; - const { p, q, repos, repoScope } = req.query as any; - if (p) { - page = parseInt(p as string, 10); - } - - const searchReq: CommitSearchRequest = { - query: q as string, - page, - repoFilters: repos ? decodeURIComponent(repos as string).split(',') : [], - repoScope: await getScope(context, repoScope), - }; - try { - const commitSearchClient = new CommitSearchClient( - new EsClientWithRequest(context, req), - log - ); - const searchRes = await commitSearchClient.search(searchReq); - return res.ok({ body: searchRes }); - } catch (error) { - return res.internalError({ body: 'Search Exception' }); - } - }, - }); -} - -async function getScope( - context: RequestHandlerContext, - repoScope: string | string[] -): Promise { - let scope: string[] = await getReferenceHelper(context.core.savedObjects.client).findReferences(); - if (typeof repoScope === 'string') { - const uriSet = new Set(repoScope.split(',')); - scope = scope.filter(uri => uriSet.has(uri)); - } else if (Array.isArray(repoScope)) { - const uriSet = new Set(repoScope); - scope = scope.filter(uri => uriSet.has(uri)); - } - return scope; -} diff --git a/x-pack/legacy/plugins/code/server/routes/setup.ts b/x-pack/legacy/plugins/code/server/routes/setup.ts deleted file mode 100644 index 6f89ebf35441f..0000000000000 --- a/x-pack/legacy/plugins/code/server/routes/setup.ts +++ /dev/null @@ -1,28 +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 { KibanaRequest, KibanaResponseFactory, RequestHandlerContext } from 'src/core/server'; - -import { CodeServerRouter } from '../security'; -import { CodeServices } from '../distributed/code_services'; -import { SetupDefinition } from '../distributed/apis'; - -export function setupRoute(router: CodeServerRouter, codeServices: CodeServices) { - const setupService = codeServices.serviceFor(SetupDefinition); - router.route({ - method: 'get', - path: '/api/code/setup', - async npHandler( - context: RequestHandlerContext, - req: KibanaRequest, - res: KibanaResponseFactory - ) { - const endpoint = await codeServices.locate(req, ''); - const setup = await setupService.setup(endpoint, {}); - return res.ok({ body: setup }); - }, - }); -} diff --git a/x-pack/legacy/plugins/code/server/routes/status.ts b/x-pack/legacy/plugins/code/server/routes/status.ts deleted file mode 100644 index e2723342b49d2..0000000000000 --- a/x-pack/legacy/plugins/code/server/routes/status.ts +++ /dev/null @@ -1,153 +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 { KibanaRequest, KibanaResponseFactory, RequestHandlerContext } from 'src/core/server'; - -import { CodeServerRouter } from '../security'; -import { LangServerType, RepoFileStatus, StatusReport } from '../../common/repo_file_status'; -import { CTAGS, LanguageServerDefinition } from '../lsp/language_servers'; -import { LanguageServerStatus } from '../../common/language_server'; -import { WorkspaceStatus } from '../lsp/request_expander'; -import { RepositoryObjectClient } from '../search'; -import { EsClientWithRequest } from '../utils/esclient_with_request'; -import { CodeServices } from '../distributed/code_services'; -import { GitServiceDefinition, LspServiceDefinition } from '../distributed/apis'; -import { Endpoint } from '../distributed/resource_locator'; -import { getReferenceHelper } from '../utils/repository_reference_helper'; - -export function statusRoute(router: CodeServerRouter, codeServices: CodeServices) { - const gitService = codeServices.serviceFor(GitServiceDefinition); - const lspService = codeServices.serviceFor(LspServiceDefinition); - async function handleRepoStatus( - endpoint: Endpoint, - report: StatusReport, - repoUri: string, - revision: string, - repoObjectClient: RepositoryObjectClient - ) { - const commit = await gitService.commit(endpoint, { - uri: repoUri, - revision: decodeURIComponent(revision), - }); - const head = await gitService.headRevision(endpoint, { uri: repoUri }); - if (head === commit.id) { - try { - const indexStatus = await repoObjectClient.getRepositoryIndexStatus(repoUri); - if (indexStatus.progress < 100) { - report.repoStatus = RepoFileStatus.INDEXING; - } - } catch (e) { - // index may not stated yet - report.repoStatus = RepoFileStatus.INDEXING; - } - } else { - report.repoStatus = RepoFileStatus.REVISION_NOT_INDEXED; - } - } - - async function handleFileStatus( - endpoint: Endpoint, - report: StatusReport, - blob: { isBinary: boolean; imageType?: string; content?: string; lang?: string } - ) { - if (blob.content) { - const lang: string = blob.lang!; - const defs = await lspService.languageSeverDef(endpoint, { lang }); - if (defs.length === 0) { - report.fileStatus = RepoFileStatus.FILE_NOT_SUPPORTED; - } else { - return defs; - } - } else { - report.fileStatus = RepoFileStatus.FILE_IS_TOO_BIG; - } - return []; - } - - async function handleLspStatus( - endpoint: Endpoint, - report: StatusReport, - defs: LanguageServerDefinition[], - repoUri: string, - revision: string - ) { - const dedicated = defs.find(d => d !== CTAGS); - const generic = defs.find(d => d === CTAGS); - report.langServerType = dedicated ? LangServerType.DEDICATED : LangServerType.GENERIC; - if ( - dedicated && - (await lspService.languageServerStatus(endpoint, { langName: dedicated.name })) === - LanguageServerStatus.NOT_INSTALLED - ) { - report.langServerStatus = RepoFileStatus.LANG_SERVER_NOT_INSTALLED; - if (generic) { - // dedicated lang server not installed, fallback to generic - report.langServerType = LangServerType.GENERIC; - } - } else { - const def = dedicated || generic; - if ( - (await lspService.languageServerStatus(endpoint, { langName: def!.name })) === - LanguageServerStatus.LAUNCH_FAILED - ) { - report.langServerStatus = RepoFileStatus.LANG_SERVER_LAUNCH_FAILED; - return; - } - const state = await lspService.initializeState(endpoint, { repoUri, revision }); - const initState = state[def!.name]; - report.langServerStatus = - initState === WorkspaceStatus.Initialized - ? RepoFileStatus.LANG_SERVER_INITIALIZED - : RepoFileStatus.LANG_SERVER_IS_INITIALIZING; - } - } - router.route({ - path: '/api/code/repo/{uri*3}/status/{ref}/{path*}', - method: 'GET', - async npHandler( - context: RequestHandlerContext, - req: KibanaRequest, - res: KibanaResponseFactory - ) { - const { uri, path, ref } = req.params as any; - const report: StatusReport = {}; - const repoObjectClient = new RepositoryObjectClient(new EsClientWithRequest(context, req)); - const endpoint = await codeServices.locate(req, uri); - - try { - // Check if the repository already exists - const repo = await repoObjectClient.getRepository(uri); - await getReferenceHelper(context.core.savedObjects.client).ensureReference(repo.uri); - } catch (e) { - return res.notFound({ body: `repo ${uri} not found` }); - } - await handleRepoStatus(endpoint, report, uri, ref, repoObjectClient); - if (path) { - try { - try { - const blob = await gitService.blob(endpoint, { - uri, - path, - revision: decodeURIComponent(ref), - }); - // text file - if (!blob.isBinary) { - const defs = await handleFileStatus(endpoint, report, blob); - if (defs.length > 0) { - await handleLspStatus(endpoint, report, defs, uri, ref); - } - } - } catch (e) { - // not a file? The path may be a dir. - } - } catch (e) { - return res.internalError({ body: e.message || e.name }); - } - } - return res.ok({ body: report }); - }, - }); -} diff --git a/x-pack/legacy/plugins/code/server/routes/workspace.ts b/x-pack/legacy/plugins/code/server/routes/workspace.ts deleted file mode 100644 index 4dfafda7369c1..0000000000000 --- a/x-pack/legacy/plugins/code/server/routes/workspace.ts +++ /dev/null @@ -1,71 +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 { KibanaRequest, KibanaResponseFactory, RequestHandlerContext } from 'src/core/server'; - -import { RequestQueryFacade } from '../../'; -import { ServerOptions } from '../server_options'; -import { CodeServerRouter } from '../security'; -import { CodeServices } from '../distributed/code_services'; -import { WorkspaceDefinition } from '../distributed/apis'; -import { getReferenceHelper } from '../utils/repository_reference_helper'; - -export function workspaceRoute( - router: CodeServerRouter, - serverOptions: ServerOptions, - codeServices: CodeServices -) { - const workspaceService = codeServices.serviceFor(WorkspaceDefinition); - - router.route({ - path: '/api/code/workspace', - method: 'GET', - async npHandler( - context: RequestHandlerContext, - req: KibanaRequest, - res: KibanaResponseFactory - ) { - return res.ok({ body: serverOptions.repoConfigs }); - }, - }); - - router.route({ - path: '/api/code/workspace/{uri*3}/{revision}', - requireAdmin: true, - method: 'POST', - async npHandler( - context: RequestHandlerContext, - req: KibanaRequest, - res: KibanaResponseFactory - ) { - const { uri: repoUri, revision } = req.params as any; - getReferenceHelper(context.core.savedObjects.client).ensureReference(repoUri); - const repoConfig = serverOptions.repoConfigs[repoUri]; - const force = !!(req.query as RequestQueryFacade).force; - if (repoConfig) { - const endpoint = await codeServices.locate(req, repoUri); - try { - await workspaceService.initCmd(endpoint, { repoUri, revision, force, repoConfig }); - return res.ok(); - } catch (e) { - if (e.isBoom) { - return res.customError({ - body: e.error, - statusCode: e.statusCode ? e.statusCode : 500, - }); - } else { - return res.customError({ - body: e.error, - statusCode: 500, - }); - } - } - } else { - return res.notFound({ body: `repo config for ${repoUri} not found.` }); - } - }, - }); -} diff --git a/x-pack/legacy/plugins/code/server/scheduler/abstract_scheduler.ts b/x-pack/legacy/plugins/code/server/scheduler/abstract_scheduler.ts deleted file mode 100644 index df4ed4eddbfac..0000000000000 --- a/x-pack/legacy/plugins/code/server/scheduler/abstract_scheduler.ts +++ /dev/null @@ -1,73 +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 { Repository } from '../../model'; -import { EsClient } from '../lib/esqueue'; -import { Poller } from '../poller'; -import { RepositoryObjectClient } from '../search'; - -export abstract class AbstractScheduler { - private poller: Poller; - - constructor( - protected readonly client: EsClient, - pollFrequencyMs: number, - protected readonly onScheduleFinished?: () => void - ) { - this.poller = new Poller({ - functionToPoll: () => { - return this.schedule(); - }, - pollFrequencyInMillis: pollFrequencyMs, - trailing: true, - continuePollingOnError: true, - pollFrequencyErrorMultiplier: 2, - }); - } - - public start() { - this.poller.start(); - } - - public stop() { - this.poller.stop(); - } - - public async schedule(): Promise { - const repoObjectClient = new RepositoryObjectClient(this.client); - const repos = await repoObjectClient.getAllRepositories(); - - const schedulingPromises = repos.map((r: Repository) => { - return this.executeSchedulingJob(r); - }); - - await Promise.all(schedulingPromises); - // Execute the callback after each schedule is done. - if (this.onScheduleFinished) { - this.onScheduleFinished(); - } - } - - protected repoNextSchedulingTime(): Date { - const duration = - this.getRepoSchedulingFrequencyMs() / 2 + - ((Math.random() * Number.MAX_SAFE_INTEGER) % this.getRepoSchedulingFrequencyMs()); - const now = new Date().getTime(); - return new Date(now + duration); - } - - protected getRepoSchedulingFrequencyMs() { - // This is an abstract class. Do nothing here. You should override this. - return -1; - } - - protected async executeSchedulingJob(repo: Repository) { - // This is an abstract class. Do nothing here. You should override this. - return new Promise((resolve, _) => { - resolve(); - }); - } -} diff --git a/x-pack/legacy/plugins/code/server/scheduler/clone_scheduler.test.ts b/x-pack/legacy/plugins/code/server/scheduler/clone_scheduler.test.ts deleted file mode 100644 index cca747af26b80..0000000000000 --- a/x-pack/legacy/plugins/code/server/scheduler/clone_scheduler.test.ts +++ /dev/null @@ -1,192 +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 fs from 'fs'; -import sinon from 'sinon'; - -import { Repository, WorkerReservedProgress, WorkerProgress } from '../../model'; -import { RepositoryDeleteStatusReservedField, RepositoryReservedField } from '../indexer/schema'; -import { EsClient } from '../lib/esqueue'; -import { Logger } from '../log'; -import { CloneWorker } from '../queue/clone_worker'; -import { ServerOptions } from '../server_options'; -import { emptyAsyncFunc } from '../test_utils'; -import { ConsoleLoggerFactory } from '../utils/console_logger_factory'; -import { CloneScheduler } from './clone_scheduler'; - -const log: Logger = new ConsoleLoggerFactory().getLogger(['test']); -const esClient = { - get: emptyAsyncFunc, - search: emptyAsyncFunc, - update: emptyAsyncFunc, -}; -const cloneWorker = { - enqueueJob: emptyAsyncFunc, -}; - -const createSearchSpy = (): sinon.SinonSpy => { - const repo: Repository = { - uri: 'github.com/elastic/code', - url: 'https://github.com/elastic/code.git', - org: 'elastic', - name: 'code', - }; - return sinon.fake.returns( - Promise.resolve({ - hits: { - hits: [ - { - _source: { - [RepositoryReservedField]: repo, - }, - }, - ], - }, - }) - ); -}; - -const createGetSpy = (inDelete: boolean): sinon.SinonStub => { - const deleteStatus: WorkerProgress = { - uri: 'github.com/elastic/code', - progress: WorkerReservedProgress.INIT, - timestamp: new Date(), - }; - const stub = sinon.stub(); - if (inDelete) { - stub.returns( - Promise.resolve({ - _source: { - [RepositoryDeleteStatusReservedField]: deleteStatus, - }, - }) - ); - } else { - stub.throwsException('Failed to get delete status'); - } - - return stub; -}; - -beforeEach(() => { - fs.mkdirSync('/tmp/github.com/elastic/code', { recursive: true }); -}); - -afterEach(() => { - fs.rmdirSync('/tmp/github.com/elastic/code'); - sinon.restore(); -}); - -test('Reschedule clone job if local repository folder does not exist', async done => { - // Setup the clone worker spy. - const enqueueJobSpy = sinon.spy(cloneWorker, 'enqueueJob'); - - // Setup the search stub to mock loading all repositories from ES. - const searchSpy = createSearchSpy(); - esClient.search = searchSpy; - - // Set up esClient - const getSpy = createGetSpy(false); - esClient.get = getSpy; - - const onScheduleFinished = () => { - try { - // Expect the search stub to be called to pull all repositories. - expect(searchSpy.calledOnce).toBeTruthy(); - expect(getSpy.calledOnce).toBeTruthy(); - // Expect a clone job has been issued to the queue. - expect(enqueueJobSpy.calledOnce).toBeTruthy(); - done(); - } catch (err) { - done.fail(err); - } - }; - - const cloneScheduler = new CloneScheduler( - (cloneWorker as any) as CloneWorker, - { - repoPath: '/tmp/does-not-exist/code', - } as ServerOptions, - esClient as EsClient, - log, - onScheduleFinished - ); - await cloneScheduler.schedule(); -}); - -test('Do not reschedule clone job if local repository folder exist', async done => { - // Setup the clone worker spy. - const enqueueJobSpy = sinon.spy(cloneWorker, 'enqueueJob'); - - // Setup the search stub to mock loading all repositories from ES. - const searchSpy = createSearchSpy(); - esClient.search = searchSpy; - - // Set up esClient - const getSpy = createGetSpy(false); - esClient.get = getSpy; - - const onScheduleFinished = () => { - try { - // Expect the search stub to be called to pull all repositories. - expect(searchSpy.calledOnce).toBeTruthy(); - expect(getSpy.calledOnce).toBeTruthy(); - // Expect no clone job submitted to the queue. - expect(enqueueJobSpy.notCalled).toBeTruthy(); - done(); - } catch (err) { - done.fail(err); - } - }; - - const cloneScheduler = new CloneScheduler( - (cloneWorker as any) as CloneWorker, - { - repoPath: '/tmp', - } as ServerOptions, - esClient as EsClient, - log, - onScheduleFinished - ); - await cloneScheduler.schedule(); -}); - -test('Do not reschedule clone job if the repository is in deletion', async done => { - // Setup the clone worker spy. - const enqueueJobSpy = sinon.spy(cloneWorker, 'enqueueJob'); - - // Setup the search stub to mock loading all repositories from ES. - const searchSpy = createSearchSpy(); - esClient.search = searchSpy; - - // Set up esClient - const getSpy = createGetSpy(true); - esClient.get = getSpy; - - const onScheduleFinished = () => { - try { - // Expect the search stub to be called to pull all repositories. - expect(searchSpy.calledOnce).toBeTruthy(); - expect(getSpy.calledOnce).toBeTruthy(); - // Expect no clone job submitted to the queue. - expect(enqueueJobSpy.notCalled).toBeTruthy(); - done(); - } catch (err) { - done.fail(err); - } - }; - - const cloneScheduler = new CloneScheduler( - (cloneWorker as any) as CloneWorker, - { - repoPath: '/tmp/does-not-exist/code', - } as ServerOptions, - esClient as EsClient, - log, - onScheduleFinished - ); - await cloneScheduler.schedule(); -}); diff --git a/x-pack/legacy/plugins/code/server/scheduler/clone_scheduler.ts b/x-pack/legacy/plugins/code/server/scheduler/clone_scheduler.ts deleted file mode 100644 index d3ed5a2649e50..0000000000000 --- a/x-pack/legacy/plugins/code/server/scheduler/clone_scheduler.ts +++ /dev/null @@ -1,73 +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 fs from 'fs'; - -import { RepositoryUtils } from '../../common/repository_utils'; -import { Repository } from '../../model'; -import { EsClient } from '../lib/esqueue'; -import { Logger } from '../log'; -import { CloneWorker } from '../queue'; -import { RepositoryObjectClient } from '../search'; -import { ServerOptions } from '../server_options'; -import { AbstractScheduler } from './abstract_scheduler'; - -// Currently, this clone schedule is only used for reclone git repository -// in case the local repository is gone for any reasons. This will only -// be scheduled once at the startup time of Kibana. -export class CloneScheduler extends AbstractScheduler { - private objectClient: RepositoryObjectClient; - - constructor( - private readonly cloneWorker: CloneWorker, - private readonly serverOptions: ServerOptions, - protected readonly client: EsClient, - protected readonly log: Logger, - protected readonly onScheduleFinished?: () => void - ) { - super(client, Number.MAX_SAFE_INTEGER, onScheduleFinished); - this.objectClient = new RepositoryObjectClient(this.client); - } - - protected getRepoSchedulingFrequencyMs() { - // We don't need this scheduler to be executed repeatedly for now. - return Number.MAX_SAFE_INTEGER; - } - - protected async executeSchedulingJob(repo: Repository) { - const { uri, url } = repo; - this.log.debug(`Try to schedule clone repo request for ${uri}`); - try { - // 1. Check if the repository is in deletion - let inDelete = false; - try { - await this.objectClient.getRepositoryDeleteStatus(repo.uri); - inDelete = true; - } catch (error) { - inDelete = false; - } - - // 2. Check if the folder exsits - const path = RepositoryUtils.repositoryLocalPath(this.serverOptions.repoPath, uri); - const repoExist = fs.existsSync(path); - - if (!inDelete && !repoExist) { - this.log.info( - `Repository does not exist on local disk. Start to schedule clone repo request for ${uri}` - ); - const payload = { url }; - await this.cloneWorker.enqueueJob(payload, {}); - } else { - this.log.debug( - `Repository ${uri} has not been fully cloned yet or in update/delete. Skip clone.` - ); - } - } catch (error) { - this.log.error(`Schedule clone for ${uri} error.`); - this.log.error(error); - } - } -} diff --git a/x-pack/legacy/plugins/code/server/scheduler/index.ts b/x-pack/legacy/plugins/code/server/scheduler/index.ts deleted file mode 100644 index 9f360ca089dcd..0000000000000 --- a/x-pack/legacy/plugins/code/server/scheduler/index.ts +++ /dev/null @@ -1,9 +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 * from './clone_scheduler'; -export * from './index_scheduler'; -export * from './update_scheduler'; diff --git a/x-pack/legacy/plugins/code/server/scheduler/index_scheduler.test.ts b/x-pack/legacy/plugins/code/server/scheduler/index_scheduler.test.ts deleted file mode 100644 index 50645dd063cb5..0000000000000 --- a/x-pack/legacy/plugins/code/server/scheduler/index_scheduler.test.ts +++ /dev/null @@ -1,353 +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 moment from 'moment'; -import sinon from 'sinon'; - -import { - CloneProgress, - CloneWorkerProgress, - Repository, - WorkerProgress, - WorkerReservedProgress, -} from '../../model'; -import { - RepositoryGitStatusReservedField, - RepositoryIndexStatusReservedField, - RepositoryReservedField, -} from '../indexer/schema'; -import { EsClient } from '../lib/esqueue'; -import { Logger } from '../log'; -import { IndexWorker } from '../queue/index_worker'; -import { ServerOptions } from '../server_options'; -import { emptyAsyncFunc } from '../test_utils'; -import { ConsoleLoggerFactory } from '../utils/console_logger_factory'; -import { IndexScheduler } from './index_scheduler'; - -const INDEX_FREQUENCY_MS: number = 1000; -const INDEX_REPO_FREQUENCY_MS: number = 8000; -const serverOpts = { - indexFrequencyMs: INDEX_FREQUENCY_MS, - indexRepoFrequencyMs: INDEX_REPO_FREQUENCY_MS, -}; -const log: Logger = new ConsoleLoggerFactory().getLogger(['test']); - -const esClient = { - get: emptyAsyncFunc, - search: emptyAsyncFunc, - update: emptyAsyncFunc, -}; -const indexWorker = { - enqueueJob: emptyAsyncFunc, -}; - -const createSearchSpy = (nextIndexTimestamp: number): sinon.SinonSpy => { - const repo: Repository = { - uri: 'github.com/elastic/code', - url: 'https://github.com/elastic/code.git', - org: 'elastic', - name: 'code', - revision: 'master', - nextIndexTimestamp: moment() - .add(nextIndexTimestamp, 'ms') - .toDate(), - }; - return sinon.fake.returns( - Promise.resolve({ - hits: { - hits: [ - { - _source: { - [RepositoryReservedField]: repo, - }, - }, - ], - }, - }) - ); -}; - -const createGetStub = ( - gitProgress: number, - lspIndexProgress: number, - lspIndexRevision: string -): sinon.SinonStub => { - const cloneStatus: CloneWorkerProgress = { - uri: 'github.com/elastic/code', - progress: gitProgress, - timestamp: new Date(), - cloneProgress: { - isCloned: true, - } as CloneProgress, - }; - const lspIndexStatus: WorkerProgress = { - uri: 'github.com/elastic/code', - progress: lspIndexProgress, - timestamp: new Date(), - revision: lspIndexRevision, - }; - const stub = sinon.stub(); - stub.onFirstCall().returns( - Promise.resolve({ - _source: { - [RepositoryGitStatusReservedField]: cloneStatus, - }, - }) - ); - stub.onSecondCall().returns( - Promise.resolve({ - _source: { - [RepositoryIndexStatusReservedField]: lspIndexStatus, - }, - }) - ); - return stub; -}; - -afterEach(() => { - sinon.restore(); -}); - -test('Next job should not execute when scheduled index time is not current.', done => { - const clock = sinon.useFakeTimers(); - - // Setup the IndexWorker spy. - const enqueueJobSpy = sinon.spy(indexWorker, 'enqueueJob'); - - // Setup the search stub to mock loading all repositories from ES. - const searchSpy = createSearchSpy(INDEX_FREQUENCY_MS + 1); - esClient.search = searchSpy; - - // Set up the update and get spies of esClient - const getSpy = sinon.spy(); - esClient.get = getSpy; - const updateSpy = sinon.spy(); - esClient.update = updateSpy; - - const onScheduleFinished = () => { - try { - // Expect the search stub to be called to pull all repositories. - expect(searchSpy.calledOnce).toBeTruthy(); - // Expect no update on anything regarding the index task scheduling. - expect(enqueueJobSpy.notCalled).toBeTruthy(); - expect(getSpy.notCalled).toBeTruthy(); - expect(updateSpy.notCalled).toBeTruthy(); - done(); - } catch (err) { - done.fail(err); - } - }; - - // Start the scheduler. - const indexScheduler = new IndexScheduler( - (indexWorker as any) as IndexWorker, - serverOpts as ServerOptions, - esClient as EsClient, - log, - onScheduleFinished - ); - indexScheduler.start(); - - // Roll the clock to the time when the first scheduled index task - // is executed. - clock.tick(INDEX_FREQUENCY_MS); -}); - -test('Next job should not execute when repo is still in clone.', done => { - const clock = sinon.useFakeTimers(); - - // Setup the IndexWorker spy. - const enqueueJobSpy = sinon.spy(indexWorker, 'enqueueJob'); - - // Setup the search stub to mock loading all repositories from ES. - const searchSpy = createSearchSpy(INDEX_FREQUENCY_MS - 1); - esClient.search = searchSpy; - - // Set up the update and get spies of esClient - const getStub = createGetStub(50, WorkerReservedProgress.COMPLETED, 'newrevision'); - esClient.get = getStub; - const updateSpy = sinon.spy(); - esClient.update = updateSpy; - - const onScheduleFinished = () => { - try { - // Expect the search stub to be called to pull all repositories and - // the get stub to be called to pull out git status. - expect(searchSpy.calledOnce).toBeTruthy(); - expect(getStub.calledOnce).toBeTruthy(); - // Expect no update on anything regarding the index task scheduling. - expect(enqueueJobSpy.notCalled).toBeTruthy(); - expect(updateSpy.notCalled).toBeTruthy(); - done(); - } catch (err) { - done.fail(err); - } - }; - - // Start the scheduler. - const indexScheduler = new IndexScheduler( - (indexWorker as any) as IndexWorker, - serverOpts as ServerOptions, - esClient as EsClient, - log, - onScheduleFinished - ); - indexScheduler.start(); - - // Roll the clock to the time when the first scheduled index task - // is executed. - clock.tick(INDEX_FREQUENCY_MS); -}); - -test('Next job should not execute when repo is still in the preivous index job.', done => { - const clock = sinon.useFakeTimers(); - - // Setup the IndexWorker spy. - const enqueueJobSpy = sinon.spy(indexWorker, 'enqueueJob'); - - // Setup the search stub to mock loading all repositories from ES. - const searchSpy = createSearchSpy(INDEX_FREQUENCY_MS - 1); - esClient.search = searchSpy; - - // Set up the update and get spies of esClient - const getStub = createGetStub(WorkerReservedProgress.COMPLETED, 50, 'newrevision'); - esClient.get = getStub; - const updateSpy = sinon.spy(); - esClient.update = updateSpy; - - const onScheduleFinished = () => { - try { - // Expect the search stub to be called to pull all repositories. - expect(searchSpy.calledOnce).toBeTruthy(); - // Expect the get stub to be called twice to pull out git status and - // lsp index status respectively. - expect(getStub.calledTwice).toBeTruthy(); - // Expect no update on anything regarding the index task scheduling. - expect(enqueueJobSpy.notCalled).toBeTruthy(); - expect(updateSpy.notCalled).toBeTruthy(); - done(); - } catch (err) { - done.fail(err); - } - }; - - // Start the scheduler. - const indexScheduler = new IndexScheduler( - (indexWorker as any) as IndexWorker, - serverOpts as ServerOptions, - esClient as EsClient, - log, - onScheduleFinished - ); - indexScheduler.start(); - - // Roll the clock to the time when the first scheduled index task - // is executed. - clock.tick(INDEX_FREQUENCY_MS); -}); - -test('Next job should not execute when repo revision did not change.', done => { - const clock = sinon.useFakeTimers(); - - // Setup the IndexWorker spy. - const enqueueJobSpy = sinon.spy(indexWorker, 'enqueueJob'); - - // Setup the search stub to mock loading all repositories from ES. - const searchSpy = createSearchSpy(INDEX_FREQUENCY_MS - 1); - esClient.search = searchSpy; - - // Set up the update and get spies of esClient - const getStub = createGetStub( - WorkerReservedProgress.COMPLETED, - WorkerReservedProgress.COMPLETED, - 'master' - ); - esClient.get = getStub; - const updateSpy = sinon.spy(); - esClient.update = updateSpy; - - const onScheduleFinished = () => { - try { - // Expect the search stub to be called to pull all repositories. - expect(searchSpy.calledOnce).toBeTruthy(); - // Expect the get stub to be called twice to pull out git status and - // lsp index status respectively. - expect(getStub.calledTwice).toBeTruthy(); - // Expect no update on anything regarding the index task scheduling. - expect(enqueueJobSpy.notCalled).toBeTruthy(); - expect(updateSpy.notCalled).toBeTruthy(); - done(); - } catch (err) { - done.fail(err); - } - }; - - // Start the scheduler. - const indexScheduler = new IndexScheduler( - (indexWorker as any) as IndexWorker, - serverOpts as ServerOptions, - esClient as EsClient, - log, - onScheduleFinished - ); - indexScheduler.start(); - - // Roll the clock to the time when the first scheduled index task - // is executed. - clock.tick(INDEX_FREQUENCY_MS); -}); - -test('Next job should execute.', done => { - const clock = sinon.useFakeTimers(); - - // Setup the IndexWorker spy. - const enqueueJobSpy = sinon.spy(indexWorker, 'enqueueJob'); - - // Setup the search stub to mock loading all repositories from ES. - const searchSpy = createSearchSpy(INDEX_FREQUENCY_MS - 1); - esClient.search = searchSpy; - - // Set up the update and get spies of esClient - const getStub = createGetStub( - WorkerReservedProgress.COMPLETED, - WorkerReservedProgress.COMPLETED, - 'newrevision' - ); - esClient.get = getStub; - const updateSpy = sinon.spy(); - esClient.update = updateSpy; - - const onScheduleFinished = () => { - try { - // Expect the search stub to be called to pull all repositories. - expect(searchSpy.calledOnce).toBeTruthy(); - // Expect the get stub to be called twice to pull out git status and - // lsp index status respectively. - expect(getStub.calledTwice).toBeTruthy(); - // Expect the update stub to be called to update next schedule timestamp. - expect(updateSpy.calledOnce).toBeTruthy(); - // Expect the enqueue job stub to be called to issue the index job. - expect(enqueueJobSpy.calledOnce).toBeTruthy(); - done(); - } catch (err) { - done.fail(err); - } - }; - - // Start the scheduler. - const indexScheduler = new IndexScheduler( - (indexWorker as any) as IndexWorker, - serverOpts as ServerOptions, - esClient as EsClient, - log, - onScheduleFinished - ); - indexScheduler.start(); - - // Roll the clock to the time when the first scheduled index task - // is executed. - clock.tick(INDEX_FREQUENCY_MS); -}); diff --git a/x-pack/legacy/plugins/code/server/scheduler/index_scheduler.ts b/x-pack/legacy/plugins/code/server/scheduler/index_scheduler.ts deleted file mode 100644 index 5319a0359739d..0000000000000 --- a/x-pack/legacy/plugins/code/server/scheduler/index_scheduler.ts +++ /dev/null @@ -1,87 +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 { RepositoryUtils } from '../../common/repository_utils'; -import { Repository, WorkerReservedProgress } from '../../model'; -import { EsClient } from '../lib/esqueue'; -import { Logger } from '../log'; -import { IndexWorker } from '../queue'; -import { RepositoryObjectClient } from '../search'; -import { ServerOptions } from '../server_options'; -import { AbstractScheduler } from './abstract_scheduler'; - -export class IndexScheduler extends AbstractScheduler { - private objectClient: RepositoryObjectClient; - - constructor( - private readonly indexWorker: IndexWorker, - private readonly serverOptions: ServerOptions, - protected readonly client: EsClient, - protected readonly log: Logger, - protected readonly onScheduleFinished?: () => void - ) { - super(client, serverOptions.indexFrequencyMs, onScheduleFinished); - this.objectClient = new RepositoryObjectClient(this.client); - } - - protected getRepoSchedulingFrequencyMs() { - return this.serverOptions.indexRepoFrequencyMs; - } - - protected async executeSchedulingJob(repo: Repository) { - this.log.debug(`Schedule index repo request for ${repo.uri}`); - try { - // This repository is too soon to execute the next index job. - if (repo.nextIndexTimestamp && new Date() < new Date(repo.nextIndexTimestamp)) { - this.log.debug(`Repo ${repo.uri} is too soon to execute the next index job.`); - return; - } - const cloneStatus = await this.objectClient.getRepositoryGitStatus(repo.uri); - if ( - !RepositoryUtils.hasFullyCloned(cloneStatus.cloneProgress) || - cloneStatus.progress !== WorkerReservedProgress.COMPLETED - ) { - this.log.debug(`Repo ${repo.uri} has not been fully cloned yet or in update. Skip index.`); - return; - } - - const repoIndexStatus = await this.objectClient.getRepositoryIndexStatus(repo.uri); - - // Schedule index job only when the indexed revision is different from the current repository - // revision. - this.log.debug( - `Current repo revision: ${repo.revision}, indexed revision ${repoIndexStatus.revision}.` - ); - if ( - repoIndexStatus.progress >= 0 && - repoIndexStatus.progress < WorkerReservedProgress.COMPLETED - ) { - this.log.debug(`Repo is still in indexing. Skip index for ${repo.uri}`); - } else if ( - repoIndexStatus.progress === WorkerReservedProgress.COMPLETED && - repoIndexStatus.revision === repo.revision - ) { - this.log.debug(`Repo does not change since last index. Skip index for ${repo.uri}.`); - } else { - const payload = { - uri: repo.uri, - revision: repo.revision, - }; - - // Update the next repo index timestamp. - const nextRepoIndexTimestamp = this.repoNextSchedulingTime(); - await this.objectClient.updateRepository(repo.uri, { - nextIndexTimestamp: nextRepoIndexTimestamp, - }); - - await this.indexWorker.enqueueJob(payload, {}); - } - } catch (error) { - this.log.error(`Schedule index job for ${repo.uri} error.`); - this.log.error(error); - } - } -} diff --git a/x-pack/legacy/plugins/code/server/scheduler/update_scheduler.test.ts b/x-pack/legacy/plugins/code/server/scheduler/update_scheduler.test.ts deleted file mode 100644 index e7721cd4ae1ac..0000000000000 --- a/x-pack/legacy/plugins/code/server/scheduler/update_scheduler.test.ts +++ /dev/null @@ -1,292 +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 moment from 'moment'; -import sinon from 'sinon'; - -import { - CloneProgress, - CloneWorkerProgress, - Repository, - WorkerReservedProgress, -} from '../../model'; -import { - RepositoryDeleteStatusReservedField, - RepositoryGitStatusReservedField, - RepositoryReservedField, -} from '../indexer/schema'; -import { EsClient } from '../lib/esqueue'; -import { Logger } from '../log'; -import { UpdateWorker } from '../queue/update_worker'; -import { ServerOptions } from '../server_options'; -import { emptyAsyncFunc } from '../test_utils'; -import { ConsoleLoggerFactory } from '../utils/console_logger_factory'; -import { UpdateScheduler } from './update_scheduler'; - -const UPDATE_FREQUENCY_MS: number = 1000; -const UPDATE_REPO_FREQUENCY_MS: number = 8000; -const serverOpts = { - updateFrequencyMs: UPDATE_FREQUENCY_MS, - updateRepoFrequencyMs: UPDATE_REPO_FREQUENCY_MS, -}; -const log: Logger = new ConsoleLoggerFactory().getLogger(['test']); - -const esClient = { - get: emptyAsyncFunc, - search: emptyAsyncFunc, - update: emptyAsyncFunc, -}; -const updateWorker = { - enqueueJob: emptyAsyncFunc, -}; - -const createSearchSpy = (nextUpdateTimestamp: number): sinon.SinonSpy => { - const repo: Repository = { - uri: 'github.com/elastic/code', - url: 'https://github.com/elastic/code.git', - org: 'elastic', - name: 'code', - nextUpdateTimestamp: moment() - .add(nextUpdateTimestamp, 'ms') - .toDate(), - }; - return sinon.fake.returns( - Promise.resolve({ - hits: { - hits: [ - { - _source: { - [RepositoryReservedField]: repo, - }, - }, - ], - }, - }) - ); -}; - -const createGetSpy = (progress: number, inDelete: boolean): sinon.SinonStub => { - const cloneStatus: CloneWorkerProgress = { - uri: 'github.com/elastic/code', - progress, - timestamp: new Date(), - cloneProgress: { - isCloned: true, - } as CloneProgress, - }; - const stub = sinon.stub(); - if (inDelete) { - stub.onFirstCall().returns( - Promise.resolve({ - _source: { - [RepositoryDeleteStatusReservedField]: { - uri: 'github.com/elastic/code', - progress: WorkerReservedProgress.INIT, - timestamp: new Date(), - }, - }, - }) - ); - } else { - stub.onFirstCall().throwsException('Failed to get delete status'); - } - stub.onSecondCall().returns( - Promise.resolve({ - _source: { - [RepositoryGitStatusReservedField]: cloneStatus, - }, - }) - ); - return stub; -}; - -afterEach(() => { - sinon.restore(); -}); - -test('Next job should not execute when scheduled update time is not current.', done => { - const clock = sinon.useFakeTimers(); - - // Setup the UpdateWorker spy. - const enqueueJobSpy = sinon.spy(updateWorker, 'enqueueJob'); - - // Setup the search stub to mock loading all repositories from ES. - const searchSpy = createSearchSpy(UPDATE_FREQUENCY_MS + 1); - esClient.search = searchSpy; - - // Set up the update and get spies of esClient - const getSpy = createGetSpy(WorkerReservedProgress.COMPLETED, false /* inDelete */); - esClient.get = getSpy; - const updateSpy = sinon.spy(); - esClient.update = updateSpy; - - const onScheduleFinished = () => { - try { - // Expect the search stub to be called to pull all repositories. - expect(searchSpy.calledOnce).toBeTruthy(); - // Expect no update on anything regarding the update task scheduling. - expect(enqueueJobSpy.notCalled).toBeTruthy(); - expect(getSpy.notCalled).toBeTruthy(); - expect(updateSpy.notCalled).toBeTruthy(); - done(); - } catch (err) { - done.fail(err); - } - }; - - // Start the scheduler. - const updateScheduler = new UpdateScheduler( - (updateWorker as any) as UpdateWorker, - serverOpts as ServerOptions, - esClient as EsClient, - log, - onScheduleFinished - ); - updateScheduler.start(); - - // Roll the clock to the time when the first scheduled update task - // is executed. - clock.tick(UPDATE_FREQUENCY_MS); -}); - -test('Next job should not execute when repo is still in clone.', done => { - const clock = sinon.useFakeTimers(); - - // Setup the UpdateWorker spy. - const enqueueJobSpy = sinon.spy(updateWorker, 'enqueueJob'); - - // Setup the search stub to mock loading all repositories from ES. - const searchSpy = createSearchSpy(UPDATE_FREQUENCY_MS - 1); - esClient.search = searchSpy; - - // Set up the update and get spies of esClient - const getSpy = createGetSpy(50, false /* inDelete */); - esClient.get = getSpy; - const updateSpy = sinon.spy(); - esClient.update = updateSpy; - - const onScheduleFinished = () => { - try { - // Expect the search stub to be called to pull all repositories and - // the get stub to be called twice to pull out git status and delete - // status. - expect(searchSpy.calledOnce).toBeTruthy(); - expect(getSpy.calledTwice).toBeTruthy(); - // Expect no update on anything regarding the update task scheduling. - expect(enqueueJobSpy.notCalled).toBeTruthy(); - expect(updateSpy.notCalled).toBeTruthy(); - done(); - } catch (err) { - done.fail(err); - } - }; - - // Start the scheduler. - const updateScheduler = new UpdateScheduler( - (updateWorker as any) as UpdateWorker, - serverOpts as ServerOptions, - esClient as EsClient, - log, - onScheduleFinished - ); - updateScheduler.start(); - - // Roll the clock to the time when the first scheduled update task - // is executed. - clock.tick(UPDATE_FREQUENCY_MS); -}); - -test('Next job should not execute when repository is in deletion', done => { - const clock = sinon.useFakeTimers(); - - // Setup the UpdateWorker spy. - const enqueueJobSpy = sinon.stub(updateWorker, 'enqueueJob'); - - // Setup the search stub to mock loading all repositories from ES. - const searchSpy = createSearchSpy(UPDATE_FREQUENCY_MS - 1); - esClient.search = searchSpy; - - // Set up the update and get spies of esClient - const getSpy = createGetSpy(WorkerReservedProgress.COMPLETED, true /* inDelete */); - esClient.get = getSpy; - const updateSpy = sinon.spy(); - esClient.update = updateSpy; - - const onScheduleFinished = () => { - try { - // Expect the search stub to be called to pull all repositories. - expect(searchSpy.calledOnce).toBeTruthy(); - expect(getSpy.calledTwice).toBeTruthy(); - // Expect no update on anything regarding the update task scheduling. - expect(enqueueJobSpy.notCalled).toBeTruthy(); - expect(updateSpy.notCalled).toBeTruthy(); - done(); - } catch (err) { - done.fail(err); - } - }; - - // Start the scheduler. - const updateScheduler = new UpdateScheduler( - (updateWorker as any) as UpdateWorker, - serverOpts as ServerOptions, - esClient as EsClient, - log, - onScheduleFinished - ); - updateScheduler.start(); - - // Roll the clock to the time when the first scheduled update task - // is executed. - clock.tick(UPDATE_FREQUENCY_MS); -}); - -test('Next job should execute.', done => { - const clock = sinon.useFakeTimers(); - - // Setup the UpdateWorker spy. - const enqueueJobSpy = sinon.stub(updateWorker, 'enqueueJob'); - - // Setup the search stub to mock loading all repositories from ES. - const searchSpy = createSearchSpy(UPDATE_FREQUENCY_MS - 1); - esClient.search = searchSpy; - - // Set up the update and get spies of esClient - const getSpy = createGetSpy(WorkerReservedProgress.COMPLETED, false /* inDelete */); - esClient.get = getSpy; - const updateSpy = sinon.spy(); - esClient.update = updateSpy; - - const onScheduleFinished = () => { - try { - // Expect the search stub to be called to pull all repositories. - expect(searchSpy.calledOnce).toBeTruthy(); - // Expect the get stub to be called to pull git status and delete status. - expect(getSpy.calledTwice).toBeTruthy(); - // Expect the update stub to be called to update next schedule timestamp. - expect(updateSpy.calledOnce).toBeTruthy(); - // Expect the enqueue job stub to be called to issue the update job. - expect(enqueueJobSpy.calledOnce).toBeTruthy(); - done(); - } catch (err) { - done.fail(err); - } - }; - - // Start the scheduler. - const updateScheduler = new UpdateScheduler( - (updateWorker as any) as UpdateWorker, - serverOpts as ServerOptions, - esClient as EsClient, - log, - onScheduleFinished - ); - updateScheduler.start(); - - // Roll the clock to the time when the first scheduled update task - // is executed. - clock.tick(UPDATE_FREQUENCY_MS); -}); diff --git a/x-pack/legacy/plugins/code/server/scheduler/update_scheduler.ts b/x-pack/legacy/plugins/code/server/scheduler/update_scheduler.ts deleted file mode 100644 index 7cc2daa0fbe60..0000000000000 --- a/x-pack/legacy/plugins/code/server/scheduler/update_scheduler.ts +++ /dev/null @@ -1,81 +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 { Repository, WorkerReservedProgress } from '../../model'; -import { EsClient } from '../lib/esqueue'; -import { Logger } from '../log'; -import { UpdateWorker } from '../queue'; -import { RepositoryObjectClient } from '../search'; -import { ServerOptions } from '../server_options'; -import { AbstractScheduler } from './abstract_scheduler'; - -export class UpdateScheduler extends AbstractScheduler { - private objectClient: RepositoryObjectClient; - - constructor( - private readonly updateWorker: UpdateWorker, - private readonly serverOptions: ServerOptions, - protected readonly client: EsClient, - protected readonly log: Logger, - protected readonly onScheduleFinished?: () => void - ) { - super(client, serverOptions.updateFrequencyMs, onScheduleFinished); - this.objectClient = new RepositoryObjectClient(this.client); - } - - protected getRepoSchedulingFrequencyMs() { - return this.serverOptions.updateRepoFrequencyMs; - } - - // TODO: Currently the schduling algorithm the most naive one, which go through - // all repositories and execute update. Later we can repeat the one we used - // before for task throttling. - protected async executeSchedulingJob(repo: Repository) { - this.log.debug(`Try to schedule update repo request for ${repo.uri}`); - try { - // This repository is too soon to execute the next update job. - if (repo.nextUpdateTimestamp && new Date() < new Date(repo.nextUpdateTimestamp)) { - this.log.debug(`Repo ${repo.uri} is too soon to execute the next update job.`); - return; - } - this.log.debug(`Start to schedule update repo request for ${repo.uri}`); - - let inDelete = false; - try { - await this.objectClient.getRepositoryDeleteStatus(repo.uri); - inDelete = true; - } catch (error) { - inDelete = false; - } - - const cloneStatus = await this.objectClient.getRepositoryGitStatus(repo.uri); - // Schedule update job only when the repo has been fully cloned already and not in delete - if ( - !inDelete && - cloneStatus.cloneProgress && - cloneStatus.cloneProgress.isCloned && - cloneStatus.progress === WorkerReservedProgress.COMPLETED - ) { - const payload = repo; - - // Update the next repo update timestamp. - const nextRepoUpdateTimestamp = this.repoNextSchedulingTime(); - await this.objectClient.updateRepository(repo.uri, { - nextUpdateTimestamp: nextRepoUpdateTimestamp, - }); - - await this.updateWorker.enqueueJob(payload, {}); - } else { - this.log.debug( - `Repo ${repo.uri} has not been fully cloned yet or in update/delete. Skip update.` - ); - } - } catch (error) { - this.log.error(`Schedule update for ${repo.uri} error.`); - this.log.error(error); - } - } -} diff --git a/x-pack/legacy/plugins/code/server/search/abstract_search_client.ts b/x-pack/legacy/plugins/code/server/search/abstract_search_client.ts deleted file mode 100644 index e0390d660fb39..0000000000000 --- a/x-pack/legacy/plugins/code/server/search/abstract_search_client.ts +++ /dev/null @@ -1,39 +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 { SearchRequest, SearchResult } from '../../model'; -import { EsClient } from '../lib/esqueue'; -import { Logger } from '../log'; -import { SearchClient } from './search_client'; - -export abstract class AbstractSearchClient implements SearchClient { - protected RESULTS_PER_PAGE = 20; - - constructor(protected readonly client: EsClient, protected readonly log: Logger) {} - - // For the full search request. - public async search(req: SearchRequest): Promise { - // This is the abstract implementation, you should override this function. - return new Promise((resolve, reject) => { - resolve(); - }); - } - - // For the typeahead suggestions request. - public async suggest(req: SearchRequest): Promise { - // This is the abstract implementation, you should override this function. - // By default, return the same result as search function above. - return this.search(req); - } - - public getResultsPerPage(req: SearchRequest): number { - let resultsPerPage = this.RESULTS_PER_PAGE; - if (req.resultsPerPage !== undefined) { - resultsPerPage = req.resultsPerPage; - } - return resultsPerPage; - } -} diff --git a/x-pack/legacy/plugins/code/server/search/commit_search_client.test.ts b/x-pack/legacy/plugins/code/server/search/commit_search_client.test.ts deleted file mode 100644 index 112263c3fa8bc..0000000000000 --- a/x-pack/legacy/plugins/code/server/search/commit_search_client.test.ts +++ /dev/null @@ -1,190 +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 sinon from 'sinon'; - -import { AnyObject, EsClient } from '../lib/esqueue'; -import { Logger } from '../log'; -import { CommitSearchClient } from './commit_search_client'; - -let commitSearchClient: CommitSearchClient; -let esClient; - -// Setup the entire RepositorySearchClient. -function initSearchClient() { - const log: Logger = (sinon.stub() as any) as Logger; - esClient = initEsClient(); - - commitSearchClient = new CommitSearchClient(esClient, log); -} - -const mockSearchResults = [ - // 1. The first response is a valid CommitSearchResult with 2 docs - { - took: 1, - hits: { - total: { - value: 2, - }, - hits: [ - { - _source: { - repoUri: 'github.com/Microsoft/TypeScript-Node-Starter', - id: '018200a626125b197573fc4d2af840af102d6ecc', - message: - 'Merge pull request #200 from peterblazejewicz/update/deps\n\nUpdate project dependencies', - body: 'Update project dependencies', - date: '2019-06-23T02:16:29.000Z', - parents: [ - 'c37be997b176292953629959e038ae88074bbfba', - 'ccf6ad89a643c321f43ec6063efa86f5a0b8a234', - ], - author: { - name: 'Orta', - email: 'ortam@microsoft.com', - }, - committer: { - name: 'GitHub', - email: 'noreply@github.com', - }, - }, - }, - { - _source: { - repoUri: 'github.com/Microsoft/TypeScript-Node-Starter', - id: 'fc4c2b2d25d51c543dc7134c12f8a825ea8d6230', - message: - 'Merge pull request #88 from peterblazejewicz/feat/update-shelljs\n\nUpdate ShellJS version', - body: 'Update ShellJS version', - date: '2018-02-27T01:09:07.000Z', - parents: [ - 'd15b403884879c505ef7c18a2f7785f4e6e67a52', - 'd0403de6cfcfa5d2477cf38baad71db15e70965c', - ], - author: { - name: 'Bowden Kelly', - email: 'wilkelly@microsoft.com', - }, - committer: { - name: 'GitHub', - email: 'noreply@github.com', - }, - }, - }, - ], - }, - aggregations: { - repoUri: { - buckets: [ - { - 'github.com/Microsoft/TypeScript-Node-Starter': 2, - }, - ], - }, - }, - }, - // 2. The second response is a valid CommitSearchResult with 0 doc - { - took: 1, - hits: { - total: { - value: 0, - }, - hits: [], - }, - aggregations: { - repoUri: { - buckets: [], - }, - }, - }, -]; - -// Setup the mock EsClient. -function initEsClient(): EsClient { - esClient = { - search: async (_: AnyObject): Promise => { - Promise.resolve({}); - }, - }; - const searchStub = sinon.stub(esClient, 'search'); - - // Binding the mock search results to the stub. - mockSearchResults.forEach((result, index) => { - searchStub.onCall(index).returns(Promise.resolve(result)); - }); - - return (esClient as any) as EsClient; -} - -beforeEach(() => { - initSearchClient(); -}); - -test('Commit search', async () => { - // 1. The first response should have 2 commits. - const responseWithResult = await commitSearchClient.search({ query: 'string', page: 1 }); - expect(responseWithResult).toEqual( - expect.objectContaining({ - total: 2, - totalPage: 1, - page: 1, - query: 'string', - commits: [ - { - repoUri: 'github.com/Microsoft/TypeScript-Node-Starter', - id: '018200a626125b197573fc4d2af840af102d6ecc', - message: - 'Merge pull request #200 from peterblazejewicz/update/deps\n\nUpdate project dependencies', - body: 'Update project dependencies', - date: '2019-06-23T02:16:29.000Z', - parents: [ - 'c37be997b176292953629959e038ae88074bbfba', - 'ccf6ad89a643c321f43ec6063efa86f5a0b8a234', - ], - author: { - name: 'Orta', - email: 'ortam@microsoft.com', - }, - committer: { - name: 'GitHub', - email: 'noreply@github.com', - }, - }, - { - repoUri: 'github.com/Microsoft/TypeScript-Node-Starter', - id: 'fc4c2b2d25d51c543dc7134c12f8a825ea8d6230', - message: - 'Merge pull request #88 from peterblazejewicz/feat/update-shelljs\n\nUpdate ShellJS version', - body: 'Update ShellJS version', - date: '2018-02-27T01:09:07.000Z', - parents: [ - 'd15b403884879c505ef7c18a2f7785f4e6e67a52', - 'd0403de6cfcfa5d2477cf38baad71db15e70965c', - ], - author: { - name: 'Bowden Kelly', - email: 'wilkelly@microsoft.com', - }, - committer: { - name: 'GitHub', - email: 'noreply@github.com', - }, - }, - ], - repoAggregations: [ - { - 'github.com/Microsoft/TypeScript-Node-Starter': 2, - }, - ], - }) - ); - - // 2. The first response should have 0 commit. - const responseWithEmptyResult = await commitSearchClient.search({ query: 'string', page: 1 }); - expect(responseWithEmptyResult.commits!.length).toEqual(0); - expect(responseWithEmptyResult.total).toEqual(0); -}); diff --git a/x-pack/legacy/plugins/code/server/search/commit_search_client.ts b/x-pack/legacy/plugins/code/server/search/commit_search_client.ts deleted file mode 100644 index 82a5e7bf5e64e..0000000000000 --- a/x-pack/legacy/plugins/code/server/search/commit_search_client.ts +++ /dev/null @@ -1,123 +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 _ from 'lodash'; - -import { - Commit, - CommitSearchRequest, - CommitSearchResult, - CommitSearchResultItem, - emptyCommitSearchResult, -} from '../../model'; -import { CommitSearchIndexWithScope, CommitIndexNamePrefix } from '../indexer/schema'; -import { EsClient } from '../lib/esqueue'; -import { Logger } from '../log'; -import { AbstractSearchClient } from './abstract_search_client'; - -export class CommitSearchClient extends AbstractSearchClient { - constructor(protected readonly client: EsClient, protected readonly log: Logger) { - super(client, log); - } - - public async search(req: CommitSearchRequest): Promise { - const resultsPerPage = this.getResultsPerPage(req); - const from = (req.page - 1) * resultsPerPage; - const size = resultsPerPage; - - const index = req.repoScope - ? CommitSearchIndexWithScope(req.repoScope) - : `${CommitIndexNamePrefix}*`; - if (index.length === 0) { - return emptyCommitSearchResult(req.query); - } - - // Post filters for repository - let repositoryPostFilters: object[] = []; - if (req.repoFilters) { - repositoryPostFilters = req.repoFilters.map((repoUri: string) => { - return { - term: { - repoUri, - }, - }; - }); - } - - const rawRes = await this.client.search({ - index, - body: { - from, - size, - query: { - bool: { - should: [ - { - match: { - message: req.query, - }, - }, - { - match: { - body: req.query, - }, - }, - ], - disable_coord: false, - adjust_pure_negative: true, - boost: 1.0, - }, - }, - post_filter: { - bool: { - should: repositoryPostFilters, - disable_coord: false, - adjust_pure_negative: true, - boost: 1.0, - }, - }, - aggregations: { - repoUri: { - terms: { - field: 'repoUri', - size: 10, - min_doc_count: 1, - shard_min_doc_count: 0, - show_term_doc_count_error: false, - order: [ - { - _count: 'desc', - }, - { - _key: 'asc', - }, - ], - }, - }, - }, - }, - }); - - const hits: any[] = rawRes.hits.hits; - const aggregations = rawRes.aggregations; - - const results: CommitSearchResultItem[] = hits.map(hit => { - const commit: Commit = hit._source; - return commit; - }); - const total = rawRes.hits.total.value; - return { - query: req.query, - from, - page: req.page, - totalPage: Math.ceil(total / resultsPerPage), - commits: results, - repoAggregations: aggregations.repoUri.buckets, - took: rawRes.took, - total, - }; - } -} diff --git a/x-pack/legacy/plugins/code/server/search/document_search_client.test.ts b/x-pack/legacy/plugins/code/server/search/document_search_client.test.ts deleted file mode 100644 index 5cc50b3c3a53f..0000000000000 --- a/x-pack/legacy/plugins/code/server/search/document_search_client.test.ts +++ /dev/null @@ -1,230 +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 sinon from 'sinon'; - -import { AnyObject, EsClient } from '../lib/esqueue'; -import { Logger } from '../log'; -import { DocumentSearchClient } from './document_search_client'; - -let docSearchClient: DocumentSearchClient; -let esClient; - -// Setup the entire DocumentSearchClient. -function initSearchClient() { - const log: Logger = (sinon.stub() as any) as Logger; - esClient = initEsClient(); - - docSearchClient = new DocumentSearchClient(esClient, log); -} - -const mockSearchResults = [ - // 1. The first response is a valid DocumentSearchResult with 2 docs - { - took: 1, - hits: { - total: { - value: 2, - }, - hits: [ - // File content matching - { - _source: { - repoUri: 'github.com/Microsoft/TypeScript-Node-Starter', - path: 'src/types/express-flash.d.ts', - content: - "\n/// \n\n// Add RequestValidation Interface on to Express's Request Interface.\ndeclare namespace Express {\n interface Request extends Flash {}\n}\n\ninterface Flash {\n flash(type: string, message: any): void;\n}\n\ndeclare module 'express-flash';\n\n", - language: 'typescript', - qnames: ['express-flash', 'Express', 'Request', 'Flash', 'flash'], - }, - highlight: { - content: [ - 'declare namespace Express {\n interface Request extends Flash {}\n}\n\ninterface Flash {\n flash(type: _@-string-@_', - ], - }, - }, - // File path matching - { - _source: { - repoUri: 'github.com/Microsoft/TypeScript-Node-Starter', - path: 'src/types/string.d.ts', - content: - 'no query in content;\nno query in content;\nno query in content;\nno query in content;\nno query in content;\n', - language: 'typescript', - qnames: ['express-flash'], - }, - highlight: { - content: [], - }, - }, - ], - }, - aggregations: { - repoUri: { - buckets: [ - { - 'github.com/Microsoft/TypeScript-Node-Starter': 2, - }, - ], - }, - language: { - buckets: [ - { - typescript: 2, - }, - ], - }, - }, - }, - // 2. The second response is a valid DocumentSearchResult with 0 doc - { - took: 1, - hits: { - total: { - value: 0, - }, - hits: [], - }, - aggregations: { - repoUri: { - buckets: [], - }, - language: { - buckets: [], - }, - }, - }, -]; - -// Setup the mock EsClient. -function initEsClient(): EsClient { - esClient = { - search: async (_: AnyObject): Promise => { - Promise.resolve({}); - }, - }; - const searchStub = sinon.stub(esClient, 'search'); - - // Binding the mock search results to the stub. - mockSearchResults.forEach((result, index) => { - searchStub.onCall(index).returns(Promise.resolve(result)); - }); - - return (esClient as any) as EsClient; -} - -beforeEach(() => { - initSearchClient(); -}); - -test('Document search', async () => { - // 1. The first response should have 1 result. - const responseWithResult = await docSearchClient.search({ query: 'string', page: 1 }); - expect(responseWithResult).toEqual( - expect.objectContaining({ - total: 2, - totalPage: 1, - page: 1, - query: 'string', - results: [ - { - uri: 'github.com/Microsoft/TypeScript-Node-Starter', - filePath: 'src/types/express-flash.d.ts', - compositeContent: { - // Content is shorted - content: '\n\ninterface Flash {\n flash(type: string, message: any): void;\n}\n\n', - // Line mapping data is populated - lineMapping: ['..', '8', '9', '10', '11', '12', '..'], - // Highlight ranges are calculated - ranges: [ - { - endColumn: 23, - endLineNumber: 4, - startColumn: 17, - startLineNumber: 4, - }, - ], - }, - language: 'typescript', - hits: 1, - }, - { - uri: 'github.com/Microsoft/TypeScript-Node-Starter', - filePath: 'src/types/string.d.ts', - compositeContent: { - // Content is shorted - content: 'no query in content;\nno query in content;\nno query in content;\n', - // Line mapping data is populated - lineMapping: ['1', '2', '3', '..'], - // Highlight ranges are calculated - ranges: [], - }, - language: 'typescript', - hits: 0, - }, - ], - repoAggregations: [ - { - 'github.com/Microsoft/TypeScript-Node-Starter': 2, - }, - ], - langAggregations: [ - { - typescript: 2, - }, - ], - }) - ); - - // 2. The first response should have 0 results. - const responseWithEmptyResult = await docSearchClient.search({ query: 'string', page: 1 }); - expect(responseWithEmptyResult.results!.length).toEqual(0); - expect(responseWithEmptyResult.total).toEqual(0); -}); - -test('Document suggest', async () => { - // 1. The first response should have 2 docs. - const responseWithResult = await docSearchClient.suggest({ query: 'string', page: 1 }); - expect(responseWithResult).toEqual( - expect.objectContaining({ - total: 2, - totalPage: 1, - page: 1, - query: 'string', - results: [ - { - uri: 'github.com/Microsoft/TypeScript-Node-Starter', - filePath: 'src/types/express-flash.d.ts', - // compositeContent field is intended to leave empty. - compositeContent: { - content: '', - lineMapping: [], - ranges: [], - }, - language: 'typescript', - hits: 0, - }, - { - uri: 'github.com/Microsoft/TypeScript-Node-Starter', - filePath: 'src/types/string.d.ts', - // compositeContent field is intended to leave empty. - compositeContent: { - content: '', - lineMapping: [], - ranges: [], - }, - language: 'typescript', - hits: 0, - }, - ], - }) - ); - - // 2. The second response should have 0 result. - const responseWithEmptyResult = await docSearchClient.suggest({ query: 'string', page: 1 }); - expect(responseWithEmptyResult.results!.length).toEqual(0); - expect(responseWithEmptyResult.total).toEqual(0); -}); diff --git a/x-pack/legacy/plugins/code/server/search/document_search_client.ts b/x-pack/legacy/plugins/code/server/search/document_search_client.ts deleted file mode 100644 index 21f19b36b1cc4..0000000000000 --- a/x-pack/legacy/plugins/code/server/search/document_search_client.ts +++ /dev/null @@ -1,412 +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 _ from 'lodash'; -import { IRange } from 'monaco-editor'; -import { LineMapper } from '../../common/line_mapper'; -import { - Document, - DocumentSearchRequest, - DocumentSearchResult, - SearchResultItem, - SourceHit, - SourceRange, - emptyDocumentSearchResult, -} from '../../model'; -import { DocumentIndexNamePrefix, DocumentSearchIndexWithScope } from '../indexer/schema'; -import { EsClient } from '../lib/esqueue'; -import { Logger } from '../log'; -import { - expandRanges, - extractSourceContent, - LineMapping, - LineRange, - mergeRanges, -} from '../utils/composite_source_merger'; -import { AbstractSearchClient } from './abstract_search_client'; - -const HIT_MERGE_LINE_INTERVAL = 2; // Inclusive -const MAX_HIT_NUMBER = 5; - -export class DocumentSearchClient extends AbstractSearchClient { - private HIGHLIGHT_PRE_TAG = '_@-'; - private HIGHLIGHT_POST_TAG = '-@_'; - protected LINE_SEPARATOR = '\n'; - - constructor(protected readonly client: EsClient, protected readonly log: Logger) { - super(client, log); - } - - public async search(req: DocumentSearchRequest): Promise { - const resultsPerPage = this.getResultsPerPage(req); - const from = (req.page - 1) * resultsPerPage; - const size = resultsPerPage; - - // The query to search qname field. - const qnameQuery = { - constant_score: { - filter: { - match: { - qnames: { - query: req.query, - operator: 'OR', - prefix_length: 0, - max_expansions: 50, - fuzzy_transpositions: true, - lenient: false, - zero_terms_query: 'NONE', - boost: 1.0, - }, - }, - }, - boost: 1.0, - }, - }; - - // The queries to search content and path filter. - const contentQuery = { - match: { - content: req.query, - }, - }; - const pathQuery = { - match: { - path: req.query, - }, - }; - - // Post filters for repository - let repositoryPostFilters: object[] = []; - if (req.repoFilters) { - repositoryPostFilters = req.repoFilters.map((repoUri: string) => { - return { - term: { - repoUri, - }, - }; - }); - } - - // Post filters for language - let languagePostFilters: object[] = []; - if (req.langFilters) { - languagePostFilters = req.langFilters.map((lang: string) => { - return { - term: { - language: lang, - }, - }; - }); - } - - const index = req.repoScope - ? DocumentSearchIndexWithScope(req.repoScope) - : `${DocumentIndexNamePrefix}*`; - if (index.length === 0) { - return emptyDocumentSearchResult(req.query); - } - - const rawRes = await this.client.search({ - index, - body: { - from, - size, - query: { - bool: { - should: [qnameQuery, contentQuery, pathQuery], - disable_coord: false, - adjust_pure_negative: true, - boost: 1.0, - }, - }, - post_filter: { - bool: { - must: [ - { - bool: { - should: repositoryPostFilters, - disable_coord: false, - adjust_pure_negative: true, - boost: 1.0, - }, - }, - { - bool: { - should: languagePostFilters, - disable_coord: false, - adjust_pure_negative: true, - boost: 1.0, - }, - }, - ], - disable_coord: false, - adjust_pure_negative: true, - boost: 1.0, - }, - }, - aggregations: { - repoUri: { - terms: { - field: 'repoUri', - size: 10, - min_doc_count: 1, - shard_min_doc_count: 0, - show_term_doc_count_error: false, - order: [ - { - _count: 'desc', - }, - { - _key: 'asc', - }, - ], - }, - }, - language: { - terms: { - field: 'language', - size: 10, - min_doc_count: 1, - shard_min_doc_count: 0, - show_term_doc_count_error: false, - order: [ - { - _count: 'desc', - }, - { - _key: 'asc', - }, - ], - }, - }, - }, - highlight: { - // TODO: we might need to improve the highlighting separator. - pre_tags: [this.HIGHLIGHT_PRE_TAG], - post_tags: [this.HIGHLIGHT_POST_TAG], - fields: { - content: {}, - path: {}, - }, - }, - }, - }); - - const hits: any[] = rawRes.hits.hits; - const aggregations = rawRes.aggregations; - const results: SearchResultItem[] = hits.map(hit => { - const doc: Document = hit._source; - const { repoUri, path, language } = doc; - - let termContent: string[] = []; - // Similar to https://github.com/lambdalab/lambdalab/blob/master/services/liaceservice/src/main/scala/com/lambdalab/liaceservice/LiaceServiceImpl.scala#L147 - // Might need refactoring. - if (hit.highlight) { - const highlightContent: string[] = hit.highlight.content; - if (highlightContent) { - highlightContent.forEach((c: string) => { - termContent = termContent.concat(this.extractKeywords(c)); - }); - } - } - const hitsContent = this.termsToHits(doc.content, termContent); - const sourceContent = this.getSourceContent(hitsContent, doc); - const item: SearchResultItem = { - uri: repoUri, - filePath: path, - language: language!, - hits: hitsContent.length, - compositeContent: sourceContent, - }; - return item; - }); - const total = rawRes.hits.total.value; - return { - query: req.query, - from, - page: req.page, - totalPage: Math.ceil(total / resultsPerPage), - results, - repoAggregations: aggregations.repoUri.buckets, - langAggregations: aggregations.language.buckets, - took: rawRes.took, - total, - }; - } - - public async suggest(req: DocumentSearchRequest): Promise { - const resultsPerPage = this.getResultsPerPage(req); - const from = (req.page - 1) * resultsPerPage; - const size = resultsPerPage; - - const index = req.repoScope - ? DocumentSearchIndexWithScope(req.repoScope) - : `${DocumentIndexNamePrefix}*`; - if (index.length === 0) { - return emptyDocumentSearchResult(req.query); - } - - const queryStr = req.query.toLowerCase(); - - const rawRes = await this.client.search({ - index, - body: { - from, - size, - query: { - bool: { - should: [ - { - prefix: { - 'path.hierarchy': { - value: queryStr, - boost: 1.0, - }, - }, - }, - { - term: { - 'path.hierarchy': { - value: queryStr, - boost: 10.0, - }, - }, - }, - ], - disable_coord: false, - adjust_pure_negative: true, - boost: 1.0, - }, - }, - }, - }); - - const hits: any[] = rawRes.hits.hits; - const results: SearchResultItem[] = hits.map(hit => { - const doc: Document = hit._source; - const { repoUri, path, language } = doc; - - const item: SearchResultItem = { - uri: repoUri, - filePath: path, - language: language!, - hits: 0, - compositeContent: { - content: '', - lineMapping: [], - ranges: [], - }, - }; - return item; - }); - const total = rawRes.hits.total.value; - return { - query: req.query, - from, - page: req.page, - totalPage: Math.ceil(total / resultsPerPage), - results, - took: rawRes.took, - total, - }; - } - - protected getSourceContent(hitsContent: SourceHit[], doc: Document) { - const docInLines = doc.content.split(this.LINE_SEPARATOR); - let slicedRanges: LineRange[] = []; - if (hitsContent.length === 0) { - // Always add a placeholder range of the first line so that for filepath - // matching search result, we will render some file content. - slicedRanges = [ - { - startLine: 0, - endLine: 0, - }, - ]; - } else { - const slicedHighlights = hitsContent.slice(0, MAX_HIT_NUMBER); - slicedRanges = slicedHighlights.map(hit => ({ - startLine: hit.range.startLoc.line, - endLine: hit.range.endLoc.line, - })); - } - - const expandedRanges = expandRanges(slicedRanges, HIT_MERGE_LINE_INTERVAL); - const mergedRanges = mergeRanges(expandedRanges); - const lineMapping = new LineMapping(); - const result = extractSourceContent(mergedRanges, docInLines, lineMapping); - const ranges: IRange[] = hitsContent - .filter(hit => lineMapping.hasLine(hit.range.startLoc.line)) - .map(hit => ({ - startColumn: hit.range.startLoc.column + 1, - startLineNumber: lineMapping.lineNumber(hit.range.startLoc.line), - endColumn: hit.range.endLoc.column + 1, - endLineNumber: lineMapping.lineNumber(hit.range.endLoc.line), - })); - return { - content: result.join(this.LINE_SEPARATOR), - lineMapping: lineMapping.toStringArray(), - ranges, - }; - } - - private termsToHits(source: string, terms: string[]): SourceHit[] { - // Dedup search terms by using Set. - const filteredTerms = new Set( - terms.filter(t => t.trim().length > 0).map(t => _.escapeRegExp(t)) - ); - if (filteredTerms.size === 0) { - return []; - } - - const lineMapper = new LineMapper(source); - const regex = new RegExp(`(${Array.from(filteredTerms.values()).join('|')})`, 'g'); - let match; - const hits: SourceHit[] = []; - do { - match = regex.exec(source); - if (match) { - const begin = match.index; - const end = regex.lastIndex; - const startLoc = lineMapper.getLocation(begin); - const endLoc = lineMapper.getLocation(end); - const range: SourceRange = { - startLoc, - endLoc, - }; - const hit: SourceHit = { - range, - score: 0.0, - term: match[1], - }; - hits.push(hit); - } - } while (match); - return hits; - } - - private extractKeywords(text: string | null): string[] { - if (!text) { - return []; - } else { - // console.log(text); - const keywordRegex = new RegExp( - `${this.HIGHLIGHT_PRE_TAG}(\\w*)${this.HIGHLIGHT_POST_TAG}`, - 'g' - ); - const keywords = text.match(keywordRegex); - if (keywords) { - return keywords.map((k: string) => { - return k - .replace(new RegExp(this.HIGHLIGHT_PRE_TAG, 'g'), '') - .replace(new RegExp(this.HIGHLIGHT_POST_TAG, 'g'), ''); - }); - } else { - return []; - } - } - } -} diff --git a/x-pack/legacy/plugins/code/server/search/index.ts b/x-pack/legacy/plugins/code/server/search/index.ts deleted file mode 100644 index 814b292f1c2d9..0000000000000 --- a/x-pack/legacy/plugins/code/server/search/index.ts +++ /dev/null @@ -1,12 +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 * from './commit_search_client'; -export * from './document_search_client'; -export * from './repository_search_client'; -export * from './symbol_search_client'; -export * from './repository_object_client'; -export * from './integrations_search_client'; diff --git a/x-pack/legacy/plugins/code/server/search/integrations_search_client.test.ts b/x-pack/legacy/plugins/code/server/search/integrations_search_client.test.ts deleted file mode 100644 index b8036934a3a2d..0000000000000 --- a/x-pack/legacy/plugins/code/server/search/integrations_search_client.test.ts +++ /dev/null @@ -1,160 +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 sinon from 'sinon'; - -import { AnyObject, EsClient } from '../lib/esqueue'; -import { Logger } from '../log'; -import { IntegrationsSearchClient } from './integrations_search_client'; - -let integSearchClient: IntegrationsSearchClient; -let esClient; - -// Setup the entire RepositorySearchClient. -function initSearchClient() { - const log: Logger = (sinon.stub() as any) as Logger; - esClient = initEsClient(); - - integSearchClient = new IntegrationsSearchClient(esClient, log); -} - -const mockSearchResults = [ - // 1. The first response is a valid DocumentSearchResult with 1 doc - { - took: 1, - hits: { - total: { - value: 1, - }, - hits: [ - { - _source: { - repoUri: 'github.com/Microsoft/TypeScript-Node-Starter', - path: 'src/types/express-flash.d.ts', - content: - "\n/// \n\n// Add RequestValidation Interface on to Express's Request Interface.\ndeclare namespace Express {\n interface Request extends Flash {}\n}\n\ninterface Flash {\n flash(type: string, message: any): void;\n}\n\ndeclare module 'express-flash';\n\n", - language: 'typescript', - qnames: ['express-flash', 'Express', 'Request', 'Flash', 'flash'], - }, - highlight: { - content: [ - 'declare namespace Express {\n interface Request extends Flash {}\n}\n\ninterface Flash {\n flash(type: _@-string-@_', - ], - }, - }, - ], - }, - }, - // 2. The second response is a valid DocumentSearchResult with 0 doc - { - took: 1, - hits: { - total: { - value: 0, - }, - hits: [], - }, - aggregations: { - repoUri: { - buckets: [], - }, - language: { - buckets: [], - }, - }, - }, - // 3. The third response is a valid DocumentSearchResult with 0 doc - { - took: 1, - hits: { - total: { - value: 0, - }, - hits: [], - }, - aggregations: { - repoUri: { - buckets: [], - }, - language: { - buckets: [], - }, - }, - }, -]; - -// Setup the mock EsClient. -function initEsClient(): EsClient { - esClient = { - search: async (_: AnyObject): Promise => { - Promise.resolve({}); - }, - }; - const searchStub = sinon.stub(esClient, 'search'); - - // Binding the mock search results to the stub. - mockSearchResults.forEach((result, index) => { - searchStub.onCall(index).returns(Promise.resolve(result)); - }); - - return (esClient as any) as EsClient; -} - -beforeEach(() => { - initSearchClient(); -}); - -test('Document search', async () => { - // 1. The first response should have 1 result. - const responseWithResult = await integSearchClient.resolveSnippets({ - repoUris: ['github.com/Microsoft/TypeScript-Node-Starter'], - filePath: 'src/types/express-flash.d.ts', - lineNumStart: 3, - lineNumEnd: 7, - }); - expect(responseWithResult).toEqual( - expect.objectContaining({ - fallback: false, - took: 1, - total: 1, - results: [ - { - uri: 'github.com/Microsoft/TypeScript-Node-Starter', - filePath: 'src/types/express-flash.d.ts', - compositeContent: { - // Content is shorted - content: - "\n/// \n\n// Add RequestValidation Interface on to Express's Request Interface.\ndeclare namespace Express {\n interface Request extends Flash {}\n}\n\ninterface Flash {\n", - // Line mapping data is populated - lineMapping: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '..'], - // Highlight ranges are calculated - ranges: [ - { - endColumn: 1, - endLineNumber: 7, - startColumn: 1, - startLineNumber: 3, - }, - ], - }, - language: 'typescript', - hits: 1, - }, - ], - }) - ); - - // 2. The first response should have 0 results. - const responseWithEmptyResult = await integSearchClient.resolveSnippets({ - repoUris: ['github.com/Microsoft/TypeScript-Node-Starter'], - filePath: 'src/types/foo-bar', - lineNumStart: 3, - lineNumEnd: 7, - }); - expect(responseWithEmptyResult.results!.length).toEqual(0); - expect(responseWithEmptyResult.total).toEqual(0); - expect(responseWithEmptyResult.fallback).toBeTruthy(); -}); diff --git a/x-pack/legacy/plugins/code/server/search/integrations_search_client.ts b/x-pack/legacy/plugins/code/server/search/integrations_search_client.ts deleted file mode 100644 index 2c0a986745429..0000000000000 --- a/x-pack/legacy/plugins/code/server/search/integrations_search_client.ts +++ /dev/null @@ -1,121 +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 { - Document, - IntegrationsSearchResult, - ResolveSnippetsRequest, - SearchResultItem, - SourceHit, - emptyIntegrationsSearchResult, -} from '../../model'; -import { DocumentSearchIndexWithScope } from '../indexer/schema'; -import { EsClient } from '../lib/esqueue'; -import { Logger } from '../log'; -import { DocumentSearchClient } from './document_search_client'; - -export class IntegrationsSearchClient extends DocumentSearchClient { - constructor(protected readonly client: EsClient, protected readonly log: Logger) { - super(client, log); - } - - public async resolveSnippets(req: ResolveSnippetsRequest): Promise { - const { repoUris, filePath, lineNumStart, lineNumEnd } = req; - const index = DocumentSearchIndexWithScope(repoUris); - if (index.length === 0) { - return emptyIntegrationsSearchResult(); - } - - let fallback = false; - let rawRes = await this.client.search({ - index, - body: { - query: { - term: { - 'path.keyword': { - value: filePath, - }, - }, - }, - }, - }); - if (rawRes.hits.hits.length === 0) { - // Fall back with match query with gauss score normalization on path length. - rawRes = await this.client.search({ - index, - body: { - query: { - script_score: { - query: { - term: { - 'path.hierarchy': { - value: filePath, - }, - }, - }, - script: { - source: - "decayNumericGauss(params.origin, params.scale, params.offset, params.decay, doc['path.keyword'].value.length())", - params: { - origin: filePath.length, - scale: 20, - offset: 0, - decay: 0.8, - }, - }, - }, - }, - }, - }); - fallback = true; - } - - const total = rawRes.hits.total.value; - const hits: any[] = rawRes.hits.hits; - const results: SearchResultItem[] = hits.map(hit => { - const doc: Document = hit._source; - const { repoUri: uri, path, language } = doc; - - const sourceContent = this.getSnippetContent(doc, lineNumStart, lineNumEnd); - const item: SearchResultItem = { - uri, - filePath: path, - language: language!, - hits: 1, - compositeContent: sourceContent, - }; - return item; - }); - - return { - results, - fallback, - took: rawRes.took, - total, - }; - } - - private getSnippetContent(doc: Document, lineNumStart: number, lineNumEnd?: number) { - const hit: SourceHit = { - range: { - startLoc: { - line: lineNumStart - 1, - column: 0, - offset: 0, - }, - endLoc: { - line: lineNumEnd === undefined ? lineNumStart - 1 : lineNumEnd - 1, - column: 0, - offset: 0, - }, - }, - score: 0, - term: '', - }; - - return super.getSourceContent([hit], doc); - } -} diff --git a/x-pack/legacy/plugins/code/server/search/repository_object_client.test.ts b/x-pack/legacy/plugins/code/server/search/repository_object_client.test.ts deleted file mode 100644 index ed64392915885..0000000000000 --- a/x-pack/legacy/plugins/code/server/search/repository_object_client.test.ts +++ /dev/null @@ -1,318 +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 sinon from 'sinon'; - -import { - CloneWorkerProgress, - Repository, - WorkerProgress, - WorkerReservedProgress, -} from '../../model'; -import { - RepositoryDeleteStatusReservedField, - RepositoryGitStatusReservedField, - RepositoryIndexName, - RepositoryIndexNamePrefix, - RepositoryIndexStatusReservedField, - RepositoryReservedField, -} from '../indexer/schema'; -import { AnyObject, EsClient } from '../lib/esqueue'; -import { RepositoryObjectClient } from './repository_object_client'; - -const esClient = { - get: async (_: AnyObject): Promise => { - Promise.resolve({}); - }, - search: async (_: AnyObject): Promise => { - Promise.resolve({}); - }, - update: async (_: AnyObject): Promise => { - Promise.resolve({}); - }, - delete: async (_: AnyObject): Promise => { - Promise.resolve({}); - }, - index: async (_: AnyObject): Promise => { - Promise.resolve({}); - }, -}; -const repoObjectClient = new RepositoryObjectClient((esClient as any) as EsClient); - -afterEach(() => { - sinon.restore(); -}); - -test('CRUD of Repository', async () => { - const repoUri = 'github.com/elastic/code'; - - // Create - const indexSpy = sinon.spy(esClient, 'index'); - const cObj: Repository = { - uri: repoUri, - url: 'https://github.com/elastic/code.git', - org: 'elastic', - name: 'code', - }; - await repoObjectClient.setRepository(repoUri, cObj); - expect(indexSpy.calledOnce).toBeTruthy(); - expect(indexSpy.getCall(0).args[0]).toEqual( - expect.objectContaining({ - index: RepositoryIndexName(repoUri), - id: RepositoryReservedField, - body: JSON.stringify({ - [RepositoryReservedField]: cObj, - }), - }) - ); - - // Read - const getFake = sinon.fake.returns( - Promise.resolve({ - _source: { - [RepositoryReservedField]: cObj, - }, - }) - ); - esClient.get = getFake; - await repoObjectClient.getRepository(repoUri); - expect(getFake.calledOnce).toBeTruthy(); - expect(getFake.getCall(0).args[0]).toEqual( - expect.objectContaining({ - index: RepositoryIndexName(repoUri), - id: RepositoryReservedField, - }) - ); - - // Update - const updateSpy = sinon.spy(esClient, 'update'); - const uObj = { - url: 'https://github.com/elastic/codesearch.git', - }; - await repoObjectClient.updateRepository(repoUri, uObj); - expect(updateSpy.calledOnce).toBeTruthy(); - expect(updateSpy.getCall(0).args[0]).toEqual( - expect.objectContaining({ - index: RepositoryIndexName(repoUri), - id: RepositoryReservedField, - body: JSON.stringify({ - doc: { - [RepositoryReservedField]: uObj, - }, - }), - }) - ); -}); - -test('Get All Repositories', async () => { - const cObj: Repository = { - uri: 'github.com/elastic/code', - url: 'https://github.com/elastic/code.git', - org: 'elastic', - name: 'code', - }; - const searchFake = sinon.fake.returns( - Promise.resolve({ - hits: { - hits: [ - { - _source: { - [RepositoryReservedField]: cObj, - }, - }, - ], - }, - }) - ); - esClient.search = searchFake; - await repoObjectClient.getAllRepositories(); - expect(searchFake.calledOnce).toBeTruthy(); - expect(searchFake.getCall(0).args[0]).toEqual( - expect.objectContaining({ - index: `${RepositoryIndexNamePrefix}*`, - }) - ); -}); - -test('CRUD of Repository Git Status', async () => { - const repoUri = 'github.com/elastic/code'; - - // Create - const indexSpy = sinon.spy(esClient, 'index'); - const cObj: CloneWorkerProgress = { - uri: repoUri, - progress: WorkerReservedProgress.COMPLETED, - timestamp: new Date(), - }; - await repoObjectClient.setRepositoryGitStatus(repoUri, cObj); - expect(indexSpy.calledOnce).toBeTruthy(); - expect(indexSpy.getCall(0).args[0]).toEqual( - expect.objectContaining({ - index: RepositoryIndexName(repoUri), - id: RepositoryGitStatusReservedField, - body: JSON.stringify({ - [RepositoryGitStatusReservedField]: cObj, - }), - }) - ); - - // Read - const getFake = sinon.fake.returns( - Promise.resolve({ - _source: { - [RepositoryGitStatusReservedField]: cObj, - }, - }) - ); - esClient.get = getFake; - await repoObjectClient.getRepositoryGitStatus(repoUri); - expect(getFake.calledOnce).toBeTruthy(); - expect(getFake.getCall(0).args[0]).toEqual( - expect.objectContaining({ - index: RepositoryIndexName(repoUri), - id: RepositoryGitStatusReservedField, - }) - ); - - // Update - const updateSpy = sinon.spy(esClient, 'update'); - const uObj = { - progress: 50, - }; - await repoObjectClient.updateRepositoryGitStatus(repoUri, uObj); - expect(updateSpy.calledOnce).toBeTruthy(); - expect(updateSpy.getCall(0).args[0]).toEqual( - expect.objectContaining({ - index: RepositoryIndexName(repoUri), - id: RepositoryGitStatusReservedField, - body: JSON.stringify({ - doc: { - [RepositoryGitStatusReservedField]: uObj, - }, - }), - }) - ); -}); - -test('CRUD of Repository LSP Index Status', async () => { - const repoUri = 'github.com/elastic/code'; - - // Create - const indexSpy = sinon.spy(esClient, 'index'); - const cObj: WorkerProgress = { - uri: repoUri, - progress: WorkerReservedProgress.COMPLETED, - timestamp: new Date(), - }; - await repoObjectClient.setRepositoryIndexStatus(repoUri, cObj); - expect(indexSpy.calledOnce).toBeTruthy(); - expect(indexSpy.getCall(0).args[0]).toEqual( - expect.objectContaining({ - index: RepositoryIndexName(repoUri), - id: RepositoryIndexStatusReservedField, - body: JSON.stringify({ - [RepositoryIndexStatusReservedField]: cObj, - }), - }) - ); - - // Read - const getFake = sinon.fake.returns( - Promise.resolve({ - _source: { - [RepositoryIndexStatusReservedField]: cObj, - }, - }) - ); - esClient.get = getFake; - await repoObjectClient.getRepositoryIndexStatus(repoUri); - expect(getFake.calledOnce).toBeTruthy(); - expect(getFake.getCall(0).args[0]).toEqual( - expect.objectContaining({ - index: RepositoryIndexName(repoUri), - id: RepositoryIndexStatusReservedField, - }) - ); - - // Update - const updateSpy = sinon.spy(esClient, 'update'); - const uObj = { - progress: 50, - }; - await repoObjectClient.updateRepositoryIndexStatus(repoUri, uObj); - expect(updateSpy.calledOnce).toBeTruthy(); - expect(updateSpy.getCall(0).args[0]).toEqual( - expect.objectContaining({ - index: RepositoryIndexName(repoUri), - id: RepositoryIndexStatusReservedField, - body: JSON.stringify({ - doc: { - [RepositoryIndexStatusReservedField]: uObj, - }, - }), - }) - ); -}); - -test('CRUD of Repository Delete Status', async () => { - const repoUri = 'github.com/elastic/code'; - - // Create - const indexSpy = sinon.spy(esClient, 'index'); - const cObj: CloneWorkerProgress = { - uri: repoUri, - progress: WorkerReservedProgress.COMPLETED, - timestamp: new Date(), - }; - await repoObjectClient.setRepositoryDeleteStatus(repoUri, cObj); - expect(indexSpy.calledOnce).toBeTruthy(); - expect(indexSpy.getCall(0).args[0]).toEqual( - expect.objectContaining({ - index: RepositoryIndexName(repoUri), - id: RepositoryDeleteStatusReservedField, - body: JSON.stringify({ - [RepositoryDeleteStatusReservedField]: cObj, - }), - }) - ); - - // Read - const getFake = sinon.fake.returns( - Promise.resolve({ - _source: { - [RepositoryDeleteStatusReservedField]: cObj, - }, - }) - ); - esClient.get = getFake; - await repoObjectClient.getRepositoryDeleteStatus(repoUri); - expect(getFake.calledOnce).toBeTruthy(); - expect(getFake.getCall(0).args[0]).toEqual( - expect.objectContaining({ - index: RepositoryIndexName(repoUri), - id: RepositoryDeleteStatusReservedField, - }) - ); - - // Update - const updateSpy = sinon.spy(esClient, 'update'); - const uObj = { - progress: 50, - }; - await repoObjectClient.updateRepositoryDeleteStatus(repoUri, uObj); - expect(updateSpy.calledOnce).toBeTruthy(); - expect(updateSpy.getCall(0).args[0]).toEqual( - expect.objectContaining({ - index: RepositoryIndexName(repoUri), - id: RepositoryDeleteStatusReservedField, - body: JSON.stringify({ - doc: { - [RepositoryDeleteStatusReservedField]: uObj, - }, - }), - }) - ); -}); diff --git a/x-pack/legacy/plugins/code/server/search/repository_object_client.ts b/x-pack/legacy/plugins/code/server/search/repository_object_client.ts deleted file mode 100644 index 23ae73d35b8fc..0000000000000 --- a/x-pack/legacy/plugins/code/server/search/repository_object_client.ts +++ /dev/null @@ -1,167 +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 { - CloneWorkerProgress, - Repository, - RepositoryConfig, - RepositoryUri, - WorkerProgress, -} from '../../model'; -import { - RepositoryConfigReservedField, - RepositoryDeleteStatusReservedField, - RepositoryGitStatusReservedField, - RepositoryIndexName, - RepositoryIndexNamePrefix, - RepositoryIndexStatusReservedField, - RepositoryReservedField, - RepositorySearchIndexWithScope, -} from '../indexer/schema'; -import { EsClient } from '../lib/esqueue'; - -/* - * This RepositoryObjectClient is dedicated to manipulate resository related objects - * stored in ES. - */ -export class RepositoryObjectClient { - constructor(protected readonly esClient: EsClient) {} - - public async getRepositoryGitStatus(repoUri: RepositoryUri): Promise { - return await this.getRepositoryObject(repoUri, RepositoryGitStatusReservedField); - } - - public async getRepositoryIndexStatus(repoUri: RepositoryUri): Promise { - return await this.getRepositoryObject(repoUri, RepositoryIndexStatusReservedField); - } - - public async getRepositoryDeleteStatus(repoUri: RepositoryUri): Promise { - return await this.getRepositoryObject(repoUri, RepositoryDeleteStatusReservedField); - } - - public async getRepositoryConfig(repoUri: RepositoryUri): Promise { - return await this.getRepositoryObject(repoUri, RepositoryConfigReservedField); - } - - public async getRepositories(uris: string[]): Promise { - if (uris.length === 0) { - return []; - } - return this.getRepositoriesInternal(RepositorySearchIndexWithScope(uris)); - } - - public async getRepository(repoUri: RepositoryUri): Promise { - return await this.getRepositoryObject(repoUri, RepositoryReservedField); - } - - public async getAllRepositories(): Promise { - return await this.getRepositoriesInternal(`${RepositoryIndexNamePrefix}*`); - } - - private async getRepositoriesInternal(index: string) { - const res = await this.esClient.search({ - index, - body: { - query: { - exists: { - field: RepositoryReservedField, - }, - }, - }, - from: 0, - size: 10000, - }); - const hits: any[] = res.hits.hits; - const repos: Repository[] = hits.map(hit => { - const repo: Repository = hit._source[RepositoryReservedField]; - return repo; - }); - return repos; - } - - public async setRepositoryGitStatus(repoUri: RepositoryUri, gitStatus: CloneWorkerProgress) { - return await this.setRepositoryObject(repoUri, RepositoryGitStatusReservedField, gitStatus); - } - - public async setRepositoryIndexStatus(repoUri: RepositoryUri, indexStatus: WorkerProgress) { - return await this.setRepositoryObject(repoUri, RepositoryIndexStatusReservedField, indexStatus); - } - - public async setRepositoryDeleteStatus(repoUri: RepositoryUri, deleteStatus: WorkerProgress) { - return await this.setRepositoryObject( - repoUri, - RepositoryDeleteStatusReservedField, - deleteStatus - ); - } - - public async setRepositoryConfig(repoUri: RepositoryUri, config: RepositoryConfig) { - return await this.setRepositoryObject(repoUri, RepositoryConfigReservedField, config); - } - - public async setRepository(repoUri: RepositoryUri, repo: Repository) { - return await this.setRepositoryObject(repoUri, RepositoryReservedField, repo); - } - - public async updateRepositoryGitStatus(repoUri: RepositoryUri, obj: any) { - return await this.updateRepositoryObject(repoUri, RepositoryGitStatusReservedField, obj); - } - - public async updateRepositoryIndexStatus(repoUri: RepositoryUri, obj: any) { - return await this.updateRepositoryObject(repoUri, RepositoryIndexStatusReservedField, obj); - } - - public async updateRepositoryDeleteStatus(repoUri: RepositoryUri, obj: any) { - return await this.updateRepositoryObject(repoUri, RepositoryDeleteStatusReservedField, obj); - } - - public async updateRepository(repoUri: RepositoryUri, obj: any) { - return await this.updateRepositoryObject(repoUri, RepositoryReservedField, obj); - } - - private async getRepositoryObject( - repoUri: RepositoryUri, - reservedFieldName: string - ): Promise { - const res = await this.esClient.get({ - index: RepositoryIndexName(repoUri), - id: this.getRepositoryObjectId(reservedFieldName), - }); - return res._source[reservedFieldName]; - } - - private async setRepositoryObject(repoUri: RepositoryUri, reservedFieldName: string, obj: any) { - return await this.esClient.index({ - index: RepositoryIndexName(repoUri), - id: this.getRepositoryObjectId(reservedFieldName), - refresh: 'true', - body: JSON.stringify({ - [reservedFieldName]: obj, - }), - }); - } - - private async updateRepositoryObject( - repoUri: RepositoryUri, - reservedFieldName: string, - obj: any - ) { - return await this.esClient.update({ - index: RepositoryIndexName(repoUri), - id: this.getRepositoryObjectId(reservedFieldName), - refresh: 'true', - body: JSON.stringify({ - doc: { - [reservedFieldName]: obj, - }, - }), - }); - } - - private getRepositoryObjectId(reservedFieldName: string): string { - return reservedFieldName; - } -} diff --git a/x-pack/legacy/plugins/code/server/search/repository_search_client.test.ts b/x-pack/legacy/plugins/code/server/search/repository_search_client.test.ts deleted file mode 100644 index 1198ac7fb139b..0000000000000 --- a/x-pack/legacy/plugins/code/server/search/repository_search_client.test.ts +++ /dev/null @@ -1,133 +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 sinon from 'sinon'; - -import { RepositoryReservedField } from '../indexer/schema'; -import { AnyObject, EsClient } from '../lib/esqueue'; -import { Logger } from '../log'; -import { RepositorySearchClient } from './repository_search_client'; - -let repoSearchClient: RepositorySearchClient; -let esClient; - -// Setup the entire RepositorySearchClient. -function initSearchClient() { - const log: Logger = (sinon.stub() as any) as Logger; - esClient = initEsClient(); - - repoSearchClient = new RepositorySearchClient(esClient, log); -} - -const mockSearchResults = [ - // 1. The first response is a valid RepositorySearchResult with 2 repos - { - took: 1, - hits: { - total: { - value: 2, - }, - hits: [ - { - _source: { - [RepositoryReservedField]: { - uri: 'github.com/elastic/elasticsearch', - url: 'https://github.com/elastic/elasticsearch.git', - name: 'elasticsearch', - org: 'elastic', - }, - }, - }, - { - _source: { - [RepositoryReservedField]: { - uri: 'github.com/elastic/kibana', - url: 'https://github.com/elastic/kibana.git', - name: 'kibana', - org: 'elastic', - }, - }, - }, - ], - }, - }, - // 2. The second response is a valid RepositorySearchResult with 0 repos - { - took: 1, - hits: { - total: { - value: 0, - }, - hits: [], - }, - }, - // 3. The third response is an invalid RepositorySearchResult with results - // but without the RepositoryReservedField - { - took: 1, - hits: { - total: { - value: 1, - }, - hits: [ - { - _source: {}, - }, - ], - }, - }, -]; - -// Setup the mock EsClient. -function initEsClient(): EsClient { - esClient = { - search: async (_: AnyObject): Promise => { - Promise.resolve({}); - }, - }; - const searchStub = sinon.stub(esClient, 'search'); - - // Binding the mock search results to the stub. - mockSearchResults.forEach((result, index) => { - searchStub.onCall(index).returns(Promise.resolve(result)); - }); - - return (esClient as any) as EsClient; -} - -beforeEach(() => { - initSearchClient(); -}); - -test('Repository search', async () => { - // 1. The first response should have 2 results. - const responseWithResult = await repoSearchClient.search({ query: 'mockQuery', page: 1 }); - expect(responseWithResult.repositories.length).toEqual(2); - expect(responseWithResult.total).toEqual(2); - expect(responseWithResult.repositories[0]).toEqual({ - uri: 'github.com/elastic/elasticsearch', - url: 'https://github.com/elastic/elasticsearch.git', - name: 'elasticsearch', - org: 'elastic', - }); - expect(responseWithResult.repositories[1]).toEqual({ - uri: 'github.com/elastic/kibana', - url: 'https://github.com/elastic/kibana.git', - name: 'kibana', - org: 'elastic', - }); - - // 2. The second response should have 0 result. - const responseWithEmptyResult = await repoSearchClient.search({ query: 'mockQuery', page: 1 }); - expect(responseWithEmptyResult.repositories.length).toEqual(0); - expect(responseWithEmptyResult.total).toEqual(0); - - // 3. The third response should have 1 hit, but 0 RepositorySearchResults, because the result - // does not have the RepositoryReservedField. - const responseWithInvalidResult = await repoSearchClient.search({ query: 'mockQuery', page: 1 }); - expect(responseWithInvalidResult.repositories.length).toEqual(0); - expect(responseWithInvalidResult.total).toEqual(1); -}); diff --git a/x-pack/legacy/plugins/code/server/search/repository_search_client.ts b/x-pack/legacy/plugins/code/server/search/repository_search_client.ts deleted file mode 100644 index 6e6f42cbd7b59..0000000000000 --- a/x-pack/legacy/plugins/code/server/search/repository_search_client.ts +++ /dev/null @@ -1,98 +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 { - Repository, - RepositorySearchRequest, - RepositorySearchResult, - emptyRepositorySearchResult, -} from '../../model'; -import { - RepositoryIndexNamePrefix, - RepositoryReservedField, - RepositorySearchIndexWithScope, -} from '../indexer/schema'; -import { EsClient } from '../lib/esqueue'; -import { Logger } from '../log'; -import { AbstractSearchClient } from './abstract_search_client'; - -export class RepositorySearchClient extends AbstractSearchClient { - constructor(protected readonly client: EsClient, protected readonly log: Logger) { - super(client, log); - } - - public async search(req: RepositorySearchRequest): Promise { - const resultsPerPage = this.getResultsPerPage(req); - const from = (req.page - 1) * resultsPerPage; - const size = resultsPerPage; - - const index = req.repoScope - ? RepositorySearchIndexWithScope(req.repoScope) - : `${RepositoryIndexNamePrefix}*`; - - if (index.length === 0) { - return emptyRepositorySearchResult(); - } - - const queryStr = req.query.toLowerCase(); - - const rawRes = await this.client.search({ - index, - body: { - from, - size, - query: { - bool: { - should: [ - { - simple_query_string: { - query: queryStr, - fields: [ - `${RepositoryReservedField}.name^1.0`, - `${RepositoryReservedField}.org^1.0`, - ], - default_operator: 'or', - lenient: false, - analyze_wildcard: false, - boost: 1.0, - }, - }, - // This prefix query is mostly for typeahead search. - { - prefix: { - [`${RepositoryReservedField}.name`]: { - value: queryStr, - boost: 100.0, - }, - }, - }, - ], - disable_coord: false, - adjust_pure_negative: true, - boost: 1.0, - }, - }, - }, - }); - - const hits: any[] = rawRes.hits.hits; - const repos: Repository[] = hits - .filter(hit => hit._source[RepositoryReservedField]) - .map(hit => { - const repo: Repository = hit._source[RepositoryReservedField]; - return repo; - }); - const total = rawRes.hits.total.value; - return { - repositories: repos, - took: rawRes.took, - total, - from, - page: req.page, - totalPage: Math.ceil(total / resultsPerPage), - }; - } -} diff --git a/x-pack/legacy/plugins/code/server/search/search_client.ts b/x-pack/legacy/plugins/code/server/search/search_client.ts deleted file mode 100644 index a98d468d59b5f..0000000000000 --- a/x-pack/legacy/plugins/code/server/search/search_client.ts +++ /dev/null @@ -1,11 +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 { SearchRequest, SearchResult } from '../../model'; - -export interface SearchClient { - search(req: SearchRequest): Promise; -} diff --git a/x-pack/legacy/plugins/code/server/search/symbol_search_client.test.ts b/x-pack/legacy/plugins/code/server/search/symbol_search_client.test.ts deleted file mode 100644 index 7df66dc3181c0..0000000000000 --- a/x-pack/legacy/plugins/code/server/search/symbol_search_client.test.ts +++ /dev/null @@ -1,168 +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 sinon from 'sinon'; - -import { AnyObject, EsClient } from '../lib/esqueue'; -import { Logger } from '../log'; -import { SymbolSearchClient } from './symbol_search_client'; - -let symbolSearchClient: SymbolSearchClient; -let esClient; - -// Setup the entire SymbolSearchClient. -function initSearchClient() { - const log: Logger = (sinon.stub() as any) as Logger; - esClient = initEsClient(); - - symbolSearchClient = new SymbolSearchClient(esClient, log); -} - -const mockSearchResults = [ - // 1. The first response is a valid SymbolSearchResult with 2 symbols - { - took: 1, - hits: { - total: { - value: 2, - }, - hits: [ - { - _source: { - qname: 'copyStaticAssets.shell', - symbolInformation: { - name: 'shell', - kind: 13, - location: { - uri: - 'git://github.com/Microsoft/TypeScript-Node-Starter/blob/4779cb7/copyStaticAssets.ts', - range: { - start: { - line: 0, - character: 7, - }, - end: { - line: 0, - character: 17, - }, - }, - }, - containerName: 'copyStaticAssets', - }, - }, - }, - { - _source: { - qname: 'app.apiController', - symbolInformation: { - name: 'apiController', - kind: 13, - location: { - uri: 'git://github.com/Microsoft/TypeScript-Node-Starter/blob/4779cb7/src/app.ts', - range: { - start: { - line: 24, - character: 7, - }, - end: { - line: 24, - character: 25, - }, - }, - }, - containerName: 'app', - }, - }, - }, - ], - }, - }, - // 2. The second response is a valid SymbolSearchResult with 0 symbol. - { - took: 1, - hits: { - total: { - value: 0, - }, - hits: [], - }, - }, -]; - -// Setup the mock EsClient. -function initEsClient(): EsClient { - esClient = { - search: async (_: AnyObject): Promise => { - Promise.resolve({}); - }, - }; - const searchStub = sinon.stub(esClient, 'search'); - - // Binding the mock search results to the stub. - mockSearchResults.forEach((result, index) => { - searchStub.onCall(index).returns(Promise.resolve(result)); - }); - - return (esClient as any) as EsClient; -} - -beforeEach(() => { - initSearchClient(); -}); - -test('Symbol suggest/typeahead', async () => { - // 1. The first response should have 2 results. - const responseWithResult = await symbolSearchClient.suggest({ query: 'mockQuery', page: 1 }); - expect(responseWithResult.symbols.length).toEqual(2); - expect(responseWithResult.total).toEqual(2); - expect(responseWithResult.symbols[0]).toEqual({ - qname: 'copyStaticAssets.shell', - symbolInformation: { - name: 'shell', - kind: 13, - location: { - uri: 'git://github.com/Microsoft/TypeScript-Node-Starter/blob/4779cb7/copyStaticAssets.ts', - range: { - start: { - line: 0, - character: 7, - }, - end: { - line: 0, - character: 17, - }, - }, - }, - containerName: 'copyStaticAssets', - }, - }); - expect(responseWithResult.symbols[1]).toEqual({ - qname: 'app.apiController', - symbolInformation: { - name: 'apiController', - kind: 13, - location: { - uri: 'git://github.com/Microsoft/TypeScript-Node-Starter/blob/4779cb7/src/app.ts', - range: { - start: { - line: 24, - character: 7, - }, - end: { - line: 24, - character: 25, - }, - }, - }, - containerName: 'app', - }, - }); - - // 2. The second response should have 0 results. - const responseWithEmptyResult = await symbolSearchClient.suggest({ query: 'mockQuery', page: 1 }); - expect(responseWithEmptyResult.symbols.length).toEqual(0); - expect(responseWithEmptyResult.total).toEqual(0); -}); diff --git a/x-pack/legacy/plugins/code/server/search/symbol_search_client.ts b/x-pack/legacy/plugins/code/server/search/symbol_search_client.ts deleted file mode 100644 index 686961333aed0..0000000000000 --- a/x-pack/legacy/plugins/code/server/search/symbol_search_client.ts +++ /dev/null @@ -1,152 +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 { DetailSymbolInformation } from '@elastic/lsp-extension'; - -import { SymbolSearchRequest, SymbolSearchResult, emptySymbolSearchResult } from '../../model'; -import { SymbolIndexNamePrefix, SymbolSearchIndexWithScope } from '../indexer/schema'; -import { EsClient } from '../lib/esqueue'; -import { Logger } from '../log'; -import { AbstractSearchClient } from './abstract_search_client'; - -export class SymbolSearchClient extends AbstractSearchClient { - constructor(protected readonly client: EsClient, protected readonly log: Logger) { - super(client, log); - } - - public async findByQname(qname: string, repoScope: string[]): Promise { - const [from, size] = [0, 1]; - const rawRes = await this.client.search({ - index: SymbolSearchIndexWithScope(repoScope), - body: { - from, - size, - query: { - term: { - qname, - }, - }, - }, - }); - return this.handleResults(rawRes); - } - - public async suggest(req: SymbolSearchRequest): Promise { - const resultsPerPage = this.getResultsPerPage(req); - const from = (req.page - 1) * resultsPerPage; - const size = resultsPerPage; - - const index = req.repoScope - ? SymbolSearchIndexWithScope(req.repoScope) - : `${SymbolIndexNamePrefix}*`; - if (index.length === 0) { - return emptySymbolSearchResult(); - } - - const rawRes = await this.client.search({ - index, - body: { - from, - size, - query: { - bool: { - should: [ - // Boost more for case sensitive prefix query. - { - prefix: { - qname: { - value: req.query, - boost: 2.0, - }, - }, - }, - // Boost less for lowercased prefix query. - { - prefix: { - 'qname.lowercased': { - // prefix query does not apply analyzer for query. so manually lowercase the query in here. - value: req.query.toLowerCase(), - boost: 1.0, - }, - }, - }, - // Boost the exact match with case sensitive query the most. - { - term: { - qname: { - value: req.query, - boost: 20.0, - }, - }, - }, - { - term: { - 'qname.lowercased': { - // term query does not apply analyzer for query either. so manually lowercase the query in here. - value: req.query.toLowerCase(), - boost: 10.0, - }, - }, - }, - // The same applies for `symbolInformation.name` feild. - { - prefix: { - 'symbolInformation.name': { - value: req.query, - boost: 2.0, - }, - }, - }, - { - prefix: { - 'symbolInformation.name.lowercased': { - value: req.query.toLowerCase(), - boost: 1.0, - }, - }, - }, - { - term: { - 'symbolInformation.name': { - value: req.query, - boost: 20.0, - }, - }, - }, - { - term: { - 'symbolInformation.name.lowercased': { - value: req.query.toLowerCase(), - boost: 10.0, - }, - }, - }, - ], - disable_coord: false, - adjust_pure_negative: true, - boost: 1.0, - }, - }, - }, - }); - - return this.handleResults(rawRes); - } - - private handleResults(rawRes: any) { - const hits: any[] = rawRes.hits.hits; - const symbols: DetailSymbolInformation[] = hits.map(hit => { - const symbol: DetailSymbolInformation = hit._source; - return symbol; - }); - const result: SymbolSearchResult = { - symbols, - took: rawRes.took, - total: rawRes.hits.total.value, - }; - return result; - } -} diff --git a/x-pack/legacy/plugins/code/server/security.ts b/x-pack/legacy/plugins/code/server/security.ts deleted file mode 100644 index 1ed8e86d0b15e..0000000000000 --- a/x-pack/legacy/plugins/code/server/security.ts +++ /dev/null @@ -1,103 +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 { schema } from '@kbn/config-schema'; - -import { IRouter, RequestHandler } from 'src/core/server'; -import { ServerRouteFacade, RouteOptionsFacade } from '..'; - -export class CodeServerRouter { - constructor(public readonly router: IRouter) {} - - route(route: CodeRoute) { - const routeOptions: RouteOptionsFacade = (route.options || {}) as RouteOptionsFacade; - const tags = [ - ...(routeOptions.tags || []), - `access:code_${route.requireAdmin ? 'admin' : 'user'}`, - ]; - - const routeHandler = route.npHandler!; - - switch ((route.method as string).toLowerCase()) { - case 'get': { - this.router.get( - { - path: route.path, - validate: { - query: schema.object({}, { allowUnknowns: true }), - params: schema.object({}, { allowUnknowns: true }), - }, - options: { - tags, - }, - }, - routeHandler - ); - break; - } - case 'put': { - this.router.put( - { - path: route.path, - validate: { - query: schema.object({}, { allowUnknowns: true }), - params: schema.object({}, { allowUnknowns: true }), - body: schema.object({}, { allowUnknowns: true }), - }, - options: { - tags, - }, - }, - routeHandler - ); - break; - } - case 'delete': { - this.router.delete( - { - path: route.path, - validate: { - query: schema.object({}, { allowUnknowns: true }), - params: schema.object({}, { allowUnknowns: true }), - }, - options: { - tags, - }, - }, - routeHandler - ); - break; - } - case 'patch': - case 'post': { - this.router.post( - { - path: route.path, - validate: { - query: schema.object({}, { allowUnknowns: true }), - params: schema.object({}, { allowUnknowns: true }), - body: schema.object({}, { allowUnknowns: true }), - }, - options: { - tags, - }, - }, - routeHandler - ); - break; - } - default: { - throw new Error(`Unknown HTTP method: ${route.method}`); - } - } - } -} - -export interface CodeRoute extends ServerRouteFacade { - requireAdmin?: boolean; - // New Platform Route Handler API - npHandler?: RequestHandler; -} diff --git a/x-pack/legacy/plugins/code/server/server_options.ts b/x-pack/legacy/plugins/code/server/server_options.ts deleted file mode 100644 index e267e6f9146eb..0000000000000 --- a/x-pack/legacy/plugins/code/server/server_options.ts +++ /dev/null @@ -1,104 +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 { resolve } from 'path'; -import { RepoConfig, RepoConfigs } from '../model'; -import { CodeNode } from './distributed/cluster/code_nodes'; - -export interface LspOptions { - requestTimeoutMs: number; - detach: boolean; - oomScoreAdj: boolean; -} - -export interface SecurityOptions { - enableMavenImport: boolean; - enableGradleImport: boolean; - installGoDependency: boolean; - installNodeDependency: boolean; - gitHostWhitelist: string[]; - gitProtocolWhitelist: string[]; - enableJavaSecurityManager: boolean; - extraJavaRepositoryWhitelist: string[]; -} - -export interface DiskOptions { - thresholdEnabled: boolean; - watermarkLow: string; -} - -export class ServerOptions { - public readonly devMode: boolean = this.config.get('env.dev'); - - public readonly workspacePath = resolve(this.config.get('path.data'), 'code/workspace'); - - public readonly repoPath = resolve(this.config.get('path.data'), 'code/repos'); - - public readonly credsPath = resolve(this.config.get('path.data'), 'code/credentials'); - - public readonly jdtWorkspacePath = resolve(this.config.get('path.data'), 'code/jdt_ws'); - - public readonly jdtConfigPath = resolve(this.config.get('path.data'), 'code/jdt_config'); - - public readonly goPath = resolve(this.config.get('path.data'), 'code/gopath'); - - public readonly updateFrequencyMs: number = this.options.updateFrequencyMs; - - public readonly indexFrequencyMs: number = this.options.indexFrequencyMs; - - public readonly updateRepoFrequencyMs: number = this.options.updateRepoFrequencyMs; - - public readonly indexRepoFrequencyMs: number = this.options.indexRepoFrequencyMs; - - public readonly maxWorkspace: number = this.options.maxWorkspace; - - public readonly enableGlobalReference: boolean = this.options.enableGlobalReference; - - public readonly enableCommitIndexing: boolean = this.options.enableCommitIndexing; - - public readonly lsp: LspOptions = this.options.lsp; - - public readonly security: SecurityOptions = this.options.security; - - public readonly disk: DiskOptions = this.options.disk; - - public readonly repoConfigs: RepoConfigs = (this.options.repos as RepoConfig[]).reduce( - (previous, current) => { - previous[current.repo] = current; - return previous; - }, - {} as RepoConfigs - ); - - public readonly enabled: boolean = this.options.enabled; - - public readonly codeNodeUrl: string = this.options.codeNodeUrl; - - public readonly clusterEnabled: boolean = this.options.clustering.enabled; - - public readonly verbose: boolean = this.options.verbose; - - public readonly codeNodes: CodeNode[] = this.options.clustering.codeNodes; - - public readonly queueIndex: string = this.options.queueIndex; - public readonly queueTimeoutMs: number = this.options.queueTimeoutMs; - - constructor(private options: any, private config: any) {} - - /** - * TODO 'server.uuid' is not guaranteed to be loaded when the object is constructed. - * - * See [[manageUuid()]], as it was called asynchronously without actions on the completion. - */ - public get serverUUID(): string { - return this.config.get('server.uuid'); - } - - public get localAddress(): string { - const serverCfg = this.config.get('server'); - return 'http://' + serverCfg.host + ':' + serverCfg.port + serverCfg.basePath; - } -} diff --git a/x-pack/legacy/plugins/code/server/test_utils.ts b/x-pack/legacy/plugins/code/server/test_utils.ts deleted file mode 100644 index 6276515fae1b6..0000000000000 --- a/x-pack/legacy/plugins/code/server/test_utils.ts +++ /dev/null @@ -1,121 +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 fs from 'fs'; -import { Server } from 'hapi'; -import * as os from 'os'; -import path from 'path'; - -import { simplegit } from '@elastic/simple-git/dist'; -import rimraf from 'rimraf'; -import { AnyObject } from './lib/esqueue'; -import { ServerOptions } from './server_options'; -import { ServerFacade } from '..'; - -export function prepareProjectByCloning(url: string, p: string) { - return new Promise(resolve => { - if (!fs.existsSync(p)) { - rimraf(p, error => { - fs.mkdirSync(p, { recursive: true }); - const git = simplegit(p); - git.clone(url, p, ['--bare']).then(resolve); - }); - } else { - resolve(); - } - }); -} - -export async function prepareProjectByInit( - repoPath: string, - commits: { [commitMessage: string]: { [path: string]: string } } -) { - if (!fs.existsSync(repoPath)) fs.mkdirSync(repoPath, { recursive: true }); - const git = simplegit(repoPath); - await git.init(); - await git.addConfig('user.email', 'test@test.com'); - await git.addConfig('user.name', 'tester'); - const results: string[] = []; - for (const [message, commit] of Object.entries(commits)) { - const files = []; - for (const [file, content] of Object.entries(commit)) { - const filePath = path.join(repoPath, file); - const dir = path.dirname(filePath); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir, { recursive: true }); - } - fs.writeFileSync(filePath, content, 'utf8'); - files.push(file); - await git.add(file); - } - await git.commit(message, files); - const c = await git.revparse(['HEAD']); - results.push(c); - } - return { git, commits: results }; -} - -export const emptyAsyncFunc = async (_: AnyObject): Promise => { - Promise.resolve({}); -}; - -const TEST_OPTIONS = { - enabled: true, - queueIndex: '.code_internal-worker-queue', - queueTimeoutMs: 60 * 60 * 1000, // 1 hour by default - updateFreqencyMs: 5 * 60 * 1000, // 5 minutes by default - indexFrequencyMs: 24 * 60 * 60 * 1000, // 1 day by default - lsp: { - requestTimeoutMs: 5 * 60, // timeout a request over 30s - detach: false, - verbose: false, - }, - security: { - enableMavenImport: true, - enableGradleImport: true, - installNodeDependency: true, - gitProtocolWhitelist: ['ssh', 'https', 'git'], - enableJavaSecurityManager: true, - }, - disk: { - thresholdEnabled: true, - watermarkLow: '80%', - }, - clustering: { - enabled: false, - codeNodes: [], - }, - repos: [], - maxWorkspace: 5, // max workspace folder for each language server -}; - -export function createTestServerOption() { - const tmpDataPath = fs.mkdtempSync(path.join(os.tmpdir(), 'code_test')); - - const config = { - get(key: string) { - if (key === 'path.data') { - return tmpDataPath; - } - }, - }; - - return new ServerOptions(TEST_OPTIONS, config); -} - -export function createTestHapiServer() { - const server: ServerFacade = new Server(); - // @ts-ignore - server.config = () => { - return { - get(key: string) { - if (key === 'env.dev') return false; - else return true; - }, - }; - }; - return server; -} diff --git a/x-pack/legacy/plugins/code/server/usage_collector.test.ts b/x-pack/legacy/plugins/code/server/usage_collector.test.ts deleted file mode 100644 index 1b97c9dcc996b..0000000000000 --- a/x-pack/legacy/plugins/code/server/usage_collector.test.ts +++ /dev/null @@ -1,131 +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 sinon from 'sinon'; - -import { LanguageServerStatus } from '../common/language_server'; -import { RepositoryReservedField } from './indexer/schema'; -import { EsClient } from './lib/esqueue'; -import { LspService } from './lsp/lsp_service'; -import { emptyAsyncFunc } from './test_utils'; -import * as usageCollector from './usage_collector'; - -const esClient = { - search: emptyAsyncFunc, -}; - -const lspService = { - languageServerStatus: emptyAsyncFunc, -}; - -const createSearchSpy = (): sinon.SinonSpy => { - return sinon.fake.returns( - Promise.resolve({ - hits: { - hits: [ - { - _source: { - [RepositoryReservedField]: { - uri: 'github.com/elastic/code1', - }, - }, - }, - { - _source: { - [RepositoryReservedField]: { - uri: 'github.com/elastic/code2', - }, - }, - }, - ], - }, - }) - ); -}; - -describe('Code Usage Collector', () => { - let makeUsageCollectorStub: any; - let registerStub: any; - let serverStub: any; - let callClusterStub: any; - let languageServerStatusStub: any; - let searchStub: any; - - beforeEach(() => { - makeUsageCollectorStub = sinon.spy(); - registerStub = sinon.stub(); - serverStub = { - usage: { - collectorSet: { makeUsageCollector: makeUsageCollectorStub, register: registerStub }, - register: {}, - }, - }; - callClusterStub = sinon.stub(); - - searchStub = createSearchSpy(); - esClient.search = searchStub; - - languageServerStatusStub = sinon.stub(); - languageServerStatusStub.withArgs('TypeScript').returns(LanguageServerStatus.READY); - languageServerStatusStub.withArgs('Java').returns(LanguageServerStatus.READY); - languageServerStatusStub.withArgs('Ctags').returns(LanguageServerStatus.READY); - languageServerStatusStub.withArgs('Go').returns(LanguageServerStatus.NOT_INSTALLED); - lspService.languageServerStatus = languageServerStatusStub; - }); - - describe('initCodeUsageCollector', () => { - it('should call collectorSet.register', () => { - usageCollector.initCodeUsageCollector( - serverStub, - esClient as EsClient, - (lspService as any) as LspService - ); - expect(registerStub.calledOnce).toBeTruthy(); - }); - - it('should call makeUsageCollector with type = code', () => { - usageCollector.initCodeUsageCollector( - serverStub, - esClient as EsClient, - (lspService as any) as LspService - ); - expect(makeUsageCollectorStub.calledOnce).toBeTruthy(); - expect(makeUsageCollectorStub.getCall(0).args[0].type).toBe('code'); - }); - - it('should return correct stats', async () => { - usageCollector.initCodeUsageCollector( - serverStub, - esClient as EsClient, - (lspService as any) as LspService - ); - const codeStats = await makeUsageCollectorStub.getCall(0).args[0].fetch(callClusterStub); - expect(callClusterStub.notCalled).toBeTruthy(); - expect(codeStats).toEqual({ - enabled: 1, - repositories: 2, - langserver: [ - { - enabled: 1, - key: 'TypeScript', - }, - { - enabled: 1, - key: 'Java', - }, - { - enabled: 0, - key: 'Go', - }, - { - enabled: 1, - key: 'Ctags', - }, - ], - }); - }); - }); -}); diff --git a/x-pack/legacy/plugins/code/server/usage_collector.ts b/x-pack/legacy/plugins/code/server/usage_collector.ts deleted file mode 100644 index 0844235821bbb..0000000000000 --- a/x-pack/legacy/plugins/code/server/usage_collector.ts +++ /dev/null @@ -1,52 +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 { ServerFacade } from '../'; -import { APP_USAGE_TYPE } from '../common/constants'; -import { LanguageServerStatus } from '../common/language_server'; -import { CodeUsageMetrics } from '../model/usage_telemetry_metrics'; -import { EsClient } from './lib/esqueue'; -import { RepositoryObjectClient } from './search'; -import { LspService } from './lsp/lsp_service'; -import { LanguageServers, LanguageServerDefinition } from './lsp/language_servers'; - -export async function fetchCodeUsageMetrics(client: EsClient, lspService: LspService) { - const repositoryObjectClient: RepositoryObjectClient = new RepositoryObjectClient(client); - const allRepos = await repositoryObjectClient.getAllRepositories(); - const langServerEnabled = async (name: string) => { - const status = await lspService.languageServerStatus(name); - return status !== LanguageServerStatus.NOT_INSTALLED ? 1 : 0; - }; - - const langServersEnabled = await Promise.all( - LanguageServers.map(async (langServer: LanguageServerDefinition) => { - return { - key: langServer.name, - enabled: await langServerEnabled(langServer.name), - }; - }) - ); - - return { - [CodeUsageMetrics.ENABLED]: 1, - [CodeUsageMetrics.REPOSITORIES]: allRepos.length, - [CodeUsageMetrics.LANGUAGE_SERVERS]: langServersEnabled, - }; -} - -export function initCodeUsageCollector( - server: ServerFacade, - client: EsClient, - lspService: LspService -) { - const codeUsageCollector = server.usage.collectorSet.makeUsageCollector({ - type: APP_USAGE_TYPE, - isReady: () => true, - fetch: async () => fetchCodeUsageMetrics(client, lspService), - }); - - server.usage.collectorSet.register(codeUsageCollector); -} diff --git a/x-pack/legacy/plugins/code/server/utils/buffer.test.ts b/x-pack/legacy/plugins/code/server/utils/buffer.test.ts deleted file mode 100644 index d06279080958a..0000000000000 --- a/x-pack/legacy/plugins/code/server/utils/buffer.test.ts +++ /dev/null @@ -1,35 +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 { extractLines } from './buffer'; -const text = `line0 -line1 -line2 -line3 -line4`; - -const buffer = Buffer.from(text); - -test('extract first line from buffer', () => { - const lines = extractLines(buffer, 0, 1); - expect(lines).toEqual('line0'); -}); - -test('extract 2 lines from buffer', () => { - const lines = extractLines(buffer, 1, 3); - expect(lines).toEqual('line1\nline2'); -}); - -test('extract all lines from buffer', () => { - const lines = extractLines(buffer, 0, Number.MAX_VALUE); - expect(lines).toEqual(text); -}); - -test('extract at least one line', () => { - const oneLineText = Buffer.from('line0'); - const lines = extractLines(oneLineText, 0, 1); - expect(lines).toEqual('line0'); -}); diff --git a/x-pack/legacy/plugins/code/server/utils/buffer.ts b/x-pack/legacy/plugins/code/server/utils/buffer.ts deleted file mode 100644 index 67db68a0a528a..0000000000000 --- a/x-pack/legacy/plugins/code/server/utils/buffer.ts +++ /dev/null @@ -1,34 +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. - */ - -const LF = '\n'.charCodeAt(0); -const CR = '\r'.charCodeAt(0); - -export function extractLines(buf: Buffer, fromLine: number, toLine: number) { - let currentLine = 0; - let fromIdx = 0; - let toIdx = buf.length; - let lastChar = -1; - for (const [idx, char] of buf.entries()) { - if (char === LF) { - currentLine++; - if (currentLine === fromLine) { - fromIdx = idx + 1; - } - if (currentLine === toLine) { - // line-break is CRLF under windows - if (lastChar === CR) { - toIdx = idx - 1; - } else { - toIdx = idx; - } - break; - } - } - lastChar = char; - } - return buf.toString('utf8', fromIdx, toIdx); -} diff --git a/x-pack/legacy/plugins/code/server/utils/cancelable.ts b/x-pack/legacy/plugins/code/server/utils/cancelable.ts deleted file mode 100644 index 71693bf586cf7..0000000000000 --- a/x-pack/legacy/plugins/code/server/utils/cancelable.ts +++ /dev/null @@ -1,54 +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. - */ -type Resolve = (t: T) => void; -type Reject = (error: any) => void; -type Cancel = (error: any) => void; -type OnCancel = (cancel: Cancel) => void; - -export class Cancelable { - public readonly promise: Promise; - private resolve: Resolve | undefined = undefined; - private reject: Reject | undefined = undefined; - private _cancel: Cancel | undefined = undefined; - private resolved: boolean = false; - - constructor( - public readonly fn: (resolve: Resolve, reject: Reject, onCancel: OnCancel) => void - ) { - this.promise = new Promise((resolve, reject) => { - this.resolve = resolve; - this.reject = reject; - }).then((t: T) => { - this.resolved = true; - return t; - }); - fn(this.resolve!, this.reject!, (cancel: Cancel) => { - this._cancel = cancel; - }); - } - - public cancel(error: any = 'canceled'): void { - if (!this.resolved) { - if (this._cancel) { - this._cancel(error); - } else if (this.reject) { - this.reject(error); - } - } - } - - public error(error: any) { - if (this.reject) { - this.reject(error); - } - } - - public static fromPromise(promise: Promise) { - return new Cancelable((resolve, reject, c) => { - promise.then(resolve, reject); - }); - } -} diff --git a/x-pack/legacy/plugins/code/server/utils/composite_source_merger.test.ts b/x-pack/legacy/plugins/code/server/utils/composite_source_merger.test.ts deleted file mode 100644 index 91adfd6372e0e..0000000000000 --- a/x-pack/legacy/plugins/code/server/utils/composite_source_merger.test.ts +++ /dev/null @@ -1,43 +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 { - expandRanges, - extractSourceContent, - LineMapping, - mergeRanges, -} from './composite_source_merger'; - -function asRanges(array: number[][]) { - return array.map(v => { - const [startLine, endLine] = v; - return { startLine, endLine }; - }); -} - -test('expand lines to ranges', () => { - const lines = asRanges([[1, 1], [3, 3], [5, 6]]); - const expectedRanges = asRanges([[0, 4], [1, 6], [3, 9]]); - expect(expandRanges(lines, 2)).toEqual(expectedRanges); -}); - -test('merge ranges', () => { - const ranges = asRanges([[0, 3], [2, 5], [6, 7], [7, 10]]); - const expectedRanges = asRanges([[0, 5], [6, 10]]); - expect(mergeRanges(ranges)).toEqual(expectedRanges); -}); - -test('extract source by ranges', () => { - const source = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10']; - const ranges = asRanges([[0, 3], [7, 11]]); - const lineMappings = new LineMapping(); - const extracted = extractSourceContent(ranges, source, lineMappings); - expect(extracted).toEqual(['0', '1', '2', '', '7', '8', '9', '10']); - const expectedLineNumbers = ['1', '2', '3', '...', '8', '9', '10', '11']; - expect(lineMappings.toStringArray('...')).toEqual(expectedLineNumbers); - expect(lineMappings.lineNumber(7)).toBe(5); - expect(lineMappings.lineNumber(0)).toBe(1); -}); diff --git a/x-pack/legacy/plugins/code/server/utils/composite_source_merger.ts b/x-pack/legacy/plugins/code/server/utils/composite_source_merger.ts deleted file mode 100644 index 982e77a28cd93..0000000000000 --- a/x-pack/legacy/plugins/code/server/utils/composite_source_merger.ts +++ /dev/null @@ -1,133 +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 DEFAULT_LINE_NUMBER_PLACEHOLDER = '..'; - -export interface LineRange { - startLine: number; - endLine: number; -} - -/** - * expand ranges of lines, eg: - * expand 2 lines of [[3,4], [9,9]] with value 2 becomes [(1,7), (7,12)] - * @param lines the array of line numbers - * @param expand the expand range - */ -export function expandRanges(lines: LineRange[], expand: number): LineRange[] { - return lines.map(line => ({ - startLine: Math.max(0, line.startLine - expand), - endLine: line.endLine + expand + 1, - })); -} - -/** - * merge the ranges that overlap each other into one, eg: - * [(1,3), (2,5)] => [(1,5)] - * @param ranges - */ -export function mergeRanges(ranges: LineRange[]): LineRange[] { - const sortedRanges = ranges.sort((a, b) => a.startLine - b.startLine); - const results: LineRange[] = []; - - const mergeIfOverlap = (a: LineRange, b: LineRange) => { - // a <= b is always true here because sorting above - if (b.startLine >= a.startLine && b.startLine <= a.endLine) { - // overlap - if (b.endLine > a.endLine) { - a.endLine = b.endLine; // extend previous range - } - return true; - } - return false; - }; - - for (const range of sortedRanges) { - if (results.length > 0) { - const last = results[results.length - 1]; - if (mergeIfOverlap(last, range)) { - continue; - } - } - results.push(range); - } - return results; -} - -/** - * extract content from source by ranges - * @param ranges the partials ranges of contents - * @param source the source content - * @param mapper a line mapper object which contains the relationship between source content and partial content - * @return the extracted partial contents - * #todo To support server side render for grammar highlights, we could change the source parameter to HighlightedCode[]. - * #todo interface HighlightedCode { text: string, highlights: ... }; - */ -export function extractSourceContent( - ranges: LineRange[], - source: string[], - mapper: LineMapping -): string[] { - const sortedRanges = ranges.sort((a, b) => a.startLine - b.startLine); - let results: string[] = []; - const pushPlaceholder = () => { - results.push(''); - mapper.addPlaceholder(); - }; - for (const range of sortedRanges) { - if (!(results.length === 0 && range.startLine === 0)) { - pushPlaceholder(); - } - const slicedContent = source.slice(range.startLine, range.endLine); - results = results.concat(slicedContent); - mapper.addMapping(range.startLine, range.startLine + slicedContent.length); - } - const lastRange = sortedRanges[sortedRanges.length - 1]; - if (lastRange.endLine < source.length) { - pushPlaceholder(); - } - return results; -} - -export class LineMapping { - private lines: number[] = []; - private reverseMap: Map = new Map(); - public toStringArray( - placeHolder: string = DEFAULT_LINE_NUMBER_PLACEHOLDER, - startAtLine: number = 1 - ): string[] { - return this.lines.map(v => { - if (Number.isNaN(v)) { - return placeHolder; - } else { - return (v + startAtLine).toString(); - } - }); - } - public addPlaceholder() { - this.lines.push(Number.NaN); - } - - public addMapping(start: number, end: number) { - for (let i = start; i < end; i++) { - this.lines.push(i); - this.reverseMap.set(i, this.lines.length - 1); - } - } - - public lineNumber(originLineNumber: number, startAtLine: number = 1) { - const n = this.reverseMap.get(originLineNumber); - if (n === undefined) { - return Number.NaN; - } else { - return n + startAtLine; - } - } - - public hasLine(line: number) { - return this.reverseMap.has(line); - } -} diff --git a/x-pack/legacy/plugins/code/server/utils/console_logger.ts b/x-pack/legacy/plugins/code/server/utils/console_logger.ts deleted file mode 100644 index afc8677f494cf..0000000000000 --- a/x-pack/legacy/plugins/code/server/utils/console_logger.ts +++ /dev/null @@ -1,46 +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. - */ -/* eslint-disable no-console */ - -import { Logger } from '../log'; - -export class ConsoleLogger extends Logger { - constructor() { - super({ - get: (...contextParts: string[]) => { - return console as any; - }, - }); - } - - public info(msg: string | any) { - console.info(msg); - } - - public error(msg: string | any) { - console.error(msg); - } - - public log(message: string): void { - this.info(message); - } - - public debug(msg: string | any) { - console.debug(msg); - } - - public warn(msg: string | any): void { - console.warn(msg); - } - - public stdout(msg: string | any) { - console.info(msg); - } - - public stderr(msg: string | any) { - console.error(msg); - } -} diff --git a/x-pack/legacy/plugins/code/server/utils/console_logger_factory.ts b/x-pack/legacy/plugins/code/server/utils/console_logger_factory.ts deleted file mode 100644 index 01588f38a8580..0000000000000 --- a/x-pack/legacy/plugins/code/server/utils/console_logger_factory.ts +++ /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. - */ - -import { Logger } from '../log'; -import { ConsoleLogger } from './console_logger'; -import { LoggerFactory } from './log_factory'; - -export class ConsoleLoggerFactory implements LoggerFactory { - public getLogger(tags: string[] = []): Logger { - return new ConsoleLogger(); - } -} diff --git a/x-pack/legacy/plugins/code/server/utils/detect_language.test.ts b/x-pack/legacy/plugins/code/server/utils/detect_language.test.ts deleted file mode 100644 index 52cbfb24d74c8..0000000000000 --- a/x-pack/legacy/plugins/code/server/utils/detect_language.test.ts +++ /dev/null @@ -1,16 +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 { detectLanguageByFilename } from './detect_language'; - -test('detect file types', () => { - expect(detectLanguageByFilename('a.h')).toEqual('c'); - expect(detectLanguageByFilename('a.c')).toEqual('c'); - expect(detectLanguageByFilename('a.cc')).toEqual('cpp'); - expect(detectLanguageByFilename('a.m')).toEqual('objective-c'); - expect(detectLanguageByFilename('a.ts')).toEqual('typescript'); - expect(detectLanguageByFilename('a.java')).toEqual('java'); -}); diff --git a/x-pack/legacy/plugins/code/server/utils/detect_language.ts b/x-pack/legacy/plugins/code/server/utils/detect_language.ts deleted file mode 100644 index c779efef8a510..0000000000000 --- a/x-pack/legacy/plugins/code/server/utils/detect_language.ts +++ /dev/null @@ -1,61 +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 path from 'path'; -import fs from 'fs'; - -function matchAll(s: string, re: RegExp): string[][] { - let m; - const result = []; - while ((m = re.exec(s)) !== null) { - // This is necessary to avoid infinite loops with zero-width matches - if (m.index === re.lastIndex) { - re.lastIndex++; - } - result.push(m); - } - return result; -} - -const entryFilePath = require.resolve('monaco-editor/esm/vs/basic-languages/monaco.contribution'); -const languageMap: { [key: string]: string } = {}; -if (entryFilePath) { - const entryFileContent = fs.readFileSync(entryFilePath, 'utf8'); - const importRegex = /import '(.*\.contribution\.js)'/gm; - const dir = path.dirname(entryFilePath); - const regex = /id:\s*'(.*)',\s*extensions:\s*\[(.+)\]/gm; - - for (const m of matchAll(entryFileContent, importRegex)) { - const contributionFile = path.join(dir, m[1]); - if (fs.existsSync(contributionFile)) { - const contributionContent = fs.readFileSync(contributionFile, 'utf8'); - for (const mm of matchAll(contributionContent, regex)) { - const langId = mm[1]; - const extensions = mm[2]; - for (let ext of extensions.split(',')) { - ext = ext.trim().slice(1, -1); - languageMap[ext] = langId; - } - } - } - } -} -function detectByFilename(file: string): string { - const ext = path.extname(file); - if (ext) { - return languageMap[ext]; - } - return 'other'; // TODO: if how should we deal with other types? -} - -export function detectLanguageByFilename(filename: string) { - const lang = detectByFilename(filename); - return lang && lang.toLowerCase(); -} - -export async function detectLanguage(file: string, fileContent?: Buffer | string): Promise { - const lang = detectByFilename(file); - return await Promise.resolve(lang ? lang.toLowerCase() : null); -} diff --git a/x-pack/legacy/plugins/code/server/utils/es_index_client.ts b/x-pack/legacy/plugins/code/server/utils/es_index_client.ts deleted file mode 100644 index c47f57374af6f..0000000000000 --- a/x-pack/legacy/plugins/code/server/utils/es_index_client.ts +++ /dev/null @@ -1,65 +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 { - IndicesCreateParams, - IndicesDeleteParams, - IndicesExistsParams, - IndicesExistsAliasParams, - IndicesDeleteAliasParams, - IndicesGetAliasParams, - IndicesGetMappingParams, - IndicesPutAliasParams, - IndicesUpdateAliasesParams, - IndicesRefreshParams, -} from 'elasticsearch'; - -import { WithRequest } from './with_request'; -import { WithInternalRequest } from './with_internal_request'; - -export class EsIndexClient { - constructor(public readonly self: WithRequest | WithInternalRequest) {} - - public exists(params: IndicesExistsParams): Promise { - return this.self.callCluster('indices.exists', params); - } - - public create(params: IndicesCreateParams): Promise { - return this.self.callCluster('indices.create', params); - } - - public refresh(params: IndicesRefreshParams): Promise { - return this.self.callCluster('indices.refresh', params); - } - - public delete(params: IndicesDeleteParams): Promise { - return this.self.callCluster('indices.delete', params); - } - - public existsAlias(params: IndicesExistsAliasParams): Promise { - return this.self.callCluster('indices.existsAlias', params); - } - - public getAlias(params: IndicesGetAliasParams): Promise { - return this.self.callCluster('indices.getAlias', params); - } - - public putAlias(params: IndicesPutAliasParams): Promise { - return this.self.callCluster('indices.putAlias', params); - } - - public deleteAlias(params: IndicesDeleteAliasParams): Promise { - return this.self.callCluster('indices.deleteAlias', params); - } - - public updateAliases(params: IndicesUpdateAliasesParams): Promise { - return this.self.callCluster('indices.updateAliases', params); - } - - public getMapping(params: IndicesGetMappingParams): Promise { - return this.self.callCluster('indices.getMapping', params); - } -} diff --git a/x-pack/legacy/plugins/code/server/utils/esclient_with_internal_request.ts b/x-pack/legacy/plugins/code/server/utils/esclient_with_internal_request.ts deleted file mode 100644 index 60a57f4dd26ea..0000000000000 --- a/x-pack/legacy/plugins/code/server/utils/esclient_with_internal_request.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 { - BulkIndexDocumentsParams, - DeleteDocumentByQueryParams, - DeleteDocumentParams, - GetParams, - IndexDocumentParams, - ReindexParams, - SearchParams, - UpdateDocumentParams, - UpdateDocumentByQueryParams, -} from 'elasticsearch'; -import { IClusterClient } from 'src/core/server'; -import { EsClient } from '../lib/esqueue'; -import { EsIndexClient } from './es_index_client'; -import { WithInternalRequest } from './with_internal_request'; - -export class EsClientWithInternalRequest extends WithInternalRequest implements EsClient { - public readonly indices = new EsIndexClient(this); - - constructor(cluster: IClusterClient) { - super(cluster); - } - - public bulk(params: BulkIndexDocumentsParams): Promise { - return this.callCluster('bulk', params); - } - - public delete(params: DeleteDocumentParams): Promise { - return this.callCluster('delete', params); - } - - public deleteByQuery(params: DeleteDocumentByQueryParams): Promise { - return this.callCluster('deleteByQuery', params); - } - - public get(params: GetParams): Promise { - return this.callCluster('get', params); - } - - public index(params: IndexDocumentParams): Promise { - return this.callCluster('index', params); - } - - public ping(): Promise { - return this.callCluster('ping'); - } - - public reindex(params: ReindexParams): Promise { - return this.callCluster('reindex', params); - } - - public search(params: SearchParams): Promise { - return this.callCluster('search', params); - } - - public update(params: UpdateDocumentParams): Promise { - return this.callCluster('update', params); - } - - public updateByQuery(params: UpdateDocumentByQueryParams): Promise { - return this.callCluster('updateByQuery', params); - } -} diff --git a/x-pack/legacy/plugins/code/server/utils/esclient_with_request.ts b/x-pack/legacy/plugins/code/server/utils/esclient_with_request.ts deleted file mode 100644 index a685106f37c3a..0000000000000 --- a/x-pack/legacy/plugins/code/server/utils/esclient_with_request.ts +++ /dev/null @@ -1,58 +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 { KibanaRequest, RequestHandlerContext } from 'src/core/server'; -import { AnyObject, EsClient } from '../lib/esqueue'; -import { EsIndexClient } from './es_index_client'; -import { WithRequest } from './with_request'; - -export class EsClientWithRequest extends WithRequest implements EsClient { - public readonly indices = new EsIndexClient(this); - - constructor(public readonly context: RequestHandlerContext, public readonly req: KibanaRequest) { - super(context, req); - } - - public bulk(params: AnyObject): Promise { - return this.callCluster('bulk', params); - } - - public delete(params: AnyObject): Promise { - return this.callCluster('delete', params); - } - - public deleteByQuery(params: AnyObject): Promise { - return this.callCluster('deleteByQuery', params); - } - - public get(params: AnyObject): Promise { - return this.callCluster('get', params); - } - - public index(params: AnyObject): Promise { - return this.callCluster('index', params); - } - - public ping(): Promise { - return this.callCluster('ping'); - } - - public reindex(params: AnyObject): Promise { - return this.callCluster('reindex', params); - } - - public search(params: AnyObject): Promise { - return this.callCluster('search', params); - } - - public update(params: AnyObject): Promise { - return this.callCluster('update', params); - } - - public updateByQuery(params: AnyObject): Promise { - return this.callCluster('updateByQuery', params); - } -} diff --git a/x-pack/legacy/plugins/code/server/utils/format_parser.ts b/x-pack/legacy/plugins/code/server/utils/format_parser.ts deleted file mode 100644 index a09cdb20322c7..0000000000000 --- a/x-pack/legacy/plugins/code/server/utils/format_parser.ts +++ /dev/null @@ -1,64 +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 { set } from 'lodash'; - -export interface Format { - [field: string]: string | Format; -} - -export interface Field { - name: string; - path: string; - format: string; -} - -const BOUNDARY = 'ª––––––––º'; - -const SPLITTER = '≤∞≥'; - -export class FormatParser { - private fields: Field[]; - - constructor(public readonly format: Format) { - this.fields = []; - this.toFields(this.fields, format); - } - - private toFields(fields: Field[], format: Format, prefix: string = '') { - Object.entries(format).forEach(entry => { - const [key, value] = entry; - if (typeof value === 'string') { - fields.push({ - name: key, - path: `${prefix}${key}`, - format: value, - }); - } else { - this.toFields(fields, value, `${prefix}${key}.`); - } - }); - } - - public toFormatStr(): string { - return this.fields.map(f => f.format).join(SPLITTER) + BOUNDARY; - } - - public parseResult(result: string): any[] { - return result - .split(BOUNDARY) - .map(item => item.trim().split(SPLITTER)) - .filter(items => items.length > 0) - .map(items => this.toObject(items)); - } - - private toObject(items: string[]) { - const result = {}; - this.fields.forEach((f, i) => { - set(result, f.path, items[i]); - }); - return result; - } -} diff --git a/x-pack/legacy/plugins/code/server/utils/index_stats_aggregator.ts b/x-pack/legacy/plugins/code/server/utils/index_stats_aggregator.ts deleted file mode 100644 index 3b75771c32c6e..0000000000000 --- a/x-pack/legacy/plugins/code/server/utils/index_stats_aggregator.ts +++ /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 { IndexStats, IndexStatsKey } from '../../model'; - -export function aggregateIndexStats(stats: IndexStats[]): IndexStats { - const res = new Map(); - stats.forEach((s: IndexStats) => { - s.forEach((value: number, key: IndexStatsKey) => { - if (!res.has(key)) { - res.set(key, 0); - } - res.set(key, res.get(key)! + value); - }); - }); - - return res; -} diff --git a/x-pack/legacy/plugins/code/server/utils/lsp_utils.test.ts b/x-pack/legacy/plugins/code/server/utils/lsp_utils.test.ts deleted file mode 100644 index ee25000643edd..0000000000000 --- a/x-pack/legacy/plugins/code/server/utils/lsp_utils.test.ts +++ /dev/null @@ -1,54 +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 { Location } from 'vscode-languageserver-types'; -import { groupFiles } from './lsp_utils'; - -test('group files', async () => { - const range = { - start: { character: 0, line: 1 }, - end: { character: 0, line: 2 }, - }; - const url1 = 'https://github.com/elastic/code/blob/master/1'; - const url2 = 'https://github.com/elastic/code/blob/master/2'; - const url3 = 'https://github.com/elastic/code2/blob/master/1'; - const locs = [ - { - uri: url1, - range, - }, - { - uri: url1, - range: { - start: { character: 0, line: 2 }, - end: { character: 0, line: 3 }, - }, - }, - { - uri: url2, - range, - }, - { - uri: url3, - range, - }, - ] as Location[]; - const fakeSource = `1 - 2 - 3 - 4 - 5 - `; - - const fakeLoader = () => Promise.resolve({ content: fakeSource, lang: 'test' }); - // @ts-ignore - const result = await groupFiles(locs, fakeLoader); - const files = result['github.com/elastic/code']; - expect(files.length).toBe(2); - const file = files.find((f: any) => f.uri === url1); - expect(file).not.toBeUndefined(); - expect(file.lineNumbers).toStrictEqual(['1', '2', '3', '4', '5', '..']); - expect(result['github.com/elastic/code2'].length).toBe(1); -}); diff --git a/x-pack/legacy/plugins/code/server/utils/lsp_utils.ts b/x-pack/legacy/plugins/code/server/utils/lsp_utils.ts deleted file mode 100644 index 0bd6ea0ca05fc..0000000000000 --- a/x-pack/legacy/plugins/code/server/utils/lsp_utils.ts +++ /dev/null @@ -1,122 +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 { groupBy, last } from 'lodash'; -import { Location, Position } from 'vscode-languageserver-types'; -import { ResponseMessage } from 'vscode-jsonrpc/lib/messages'; -import { CTAGS, GO } from '../lsp/language_servers'; -import { - expandRanges, - extractSourceContent, - LineMapping, - mergeRanges, -} from './composite_source_merger'; -import { detectLanguage } from './detect_language'; -import { parseLspUrl } from '../../common/uri_util'; -import { GitServiceDefinition } from '../distributed/apis'; - -type SourceLoader = ( - loc: typeof GitServiceDefinition.blob.request -) => Promise; - -export interface File { - repo: string; - file: string; - language: string; - uri: string; - revision: string; - code: string; - lineNumbers: number[]; - highlights: any[]; -} - -export interface GroupedFiles { - [repo: string]: File[]; -} - -export async function groupFiles( - list: Location[], - sourceLoader: SourceLoader -): Promise { - const files = []; - const groupedLocations = groupBy(list, 'uri'); - for (const url of Object.keys(groupedLocations)) { - const { repoUri, revision, file } = parseLspUrl(url)!; - const locations: Location[] = groupedLocations[url]; - const lines = locations.map(l => ({ - startLine: l.range.start.line, - endLine: l.range.end.line, - })); - const ranges = expandRanges(lines, 1); - const mergedRanges = mergeRanges(ranges); - try { - const blob = await sourceLoader({ uri: repoUri, path: file!, revision }); - if (blob.content) { - const source = blob.content.split('\n'); - const language = blob.lang; - const lineMappings = new LineMapping(); - const code = extractSourceContent(mergedRanges, source, lineMappings).join('\n'); - const lineNumbers = lineMappings.toStringArray(); - const highlights = locations.map(l => { - const { start, end } = l.range; - const startLineNumber = lineMappings.lineNumber(start.line); - const endLineNumber = lineMappings.lineNumber(end.line); - return { - startLineNumber, - startColumn: start.character + 1, - endLineNumber, - endColumn: end.character + 1, - }; - }); - files.push({ - repo: repoUri, - file, - language, - uri: url, - revision, - code, - lineNumbers, - highlights, - }); - } - } catch (e) { - // can't load this file, ignore this result - } - } - return (groupBy(files, 'repo') as unknown) as GroupedFiles; -} - -export async function findTitleFromHover(hover: ResponseMessage, uri: string, position: Position) { - let title: string; - if (hover.result && hover.result.contents) { - if (Array.isArray(hover.result.contents)) { - const content = hover.result.contents[0]; - title = hover.result.contents[0].value; - const lang = await detectLanguage(uri.replace('file://', '')); - // TODO(henrywong) Find a gernal approach to construct the reference title. - if (content.kind) { - // The format of the hover result is 'MarkupContent', extract appropriate pieces as the references title. - if (GO.languages.includes(lang)) { - title = title.substring(title.indexOf('```go\n') + 5, title.lastIndexOf('\n```')); - if (title.includes('{\n')) { - title = title.substring(0, title.indexOf('{\n')); - } - } - } else if (CTAGS.languages.includes(lang)) { - // There are language servers may provide hover results with markdown syntax, like ctags-langserver, - // extract the plain text. - if (title.substring(0, 2) === '**' && title.includes('**\n')) { - title = title.substring(title.indexOf('**\n') + 3); - } - } - } else { - title = hover.result.contents as 'string'; - } - } else { - title = last(uri.split('/')) + `(${position.line}, ${position.character})`; - } - return title; -} diff --git a/x-pack/legacy/plugins/code/server/utils/repository_reference_helper.ts b/x-pack/legacy/plugins/code/server/utils/repository_reference_helper.ts deleted file mode 100644 index dcbd551a5eac2..0000000000000 --- a/x-pack/legacy/plugins/code/server/utils/repository_reference_helper.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 { SavedObjectsClientContract } from 'src/core/server'; -import Boom from 'boom'; -import { SAVED_OBJ_REPO } from '../../common/constants'; - -export interface RepositoryReferenceHelper { - /** - * Creates a reference from the current namespace to the given repository. - * - * @param uri The uri of the repository. - * @returns true if there is no other references for the repository, false otherwise. - */ - createReference(uri: string): Promise; - - /** - * Checks whether there is a reference from the current namespace to the given repository. - * - * @param uri The uri of the repository. - * @returns true if there is a reference from the current namespace to the given repository, false otherwise. - */ - hasReference(uri: string): Promise; - - /** - * Throws an error if there is no reference from the current namespace to the given repository. - * there is none. - * - * @param uri The uri of the repository. - */ - ensureReference(uri: string): Promise; - - /** - * Deletes the reference from the current namespace to the given repository. - * - * @param uri The uri of the repository. - * @returns True if there is no more references to the repository after the deletion. - */ - deleteReference(uri: string): Promise; - - /** - * Finds all repository uris of which the current namespace has references. - * - * @returns uris the current namespace references to. - */ - findReferences(): Promise; -} - -/** - * A factory function helps to create a appropriate helper instance. - * - * @param client A saved objects client instance. - * @returns An helper instance. - */ -export function getReferenceHelper(client: SavedObjectsClientContract): RepositoryReferenceHelper { - return new DefaultReferenceHelper(client); -} - -class DefaultReferenceHelper implements RepositoryReferenceHelper { - constructor(private readonly client: SavedObjectsClientContract) {} - - async createReference(uri: string): Promise { - try { - await this.client.create( - SAVED_OBJ_REPO, - { - uri, - }, - { - id: uri, - } - ); - return true; - } catch (e) { - if (Boom.isBoom(e) && e.output.statusCode === 409) { - return false; - } - throw e; - } - } - - async deleteReference(uri: string): Promise { - await this.client.delete(SAVED_OBJ_REPO, uri); - return true; - } - - async ensureReference(uri: string): Promise { - await this.client.get(SAVED_OBJ_REPO, uri); - } - - async hasReference(uri: string): Promise { - try { - await this.ensureReference(uri); - return true; - } catch (e) { - if (Boom.isBoom(e) && e.output.statusCode === 404) { - return false; - } - throw e; - } - } - - async findReferences(): Promise { - const resp = await this.client.find({ - type: SAVED_OBJ_REPO, - }); - return resp.saved_objects.map(obj => obj.attributes.uri as string); - } -} diff --git a/x-pack/legacy/plugins/code/server/utils/server_logger_factory.ts b/x-pack/legacy/plugins/code/server/utils/server_logger_factory.ts deleted file mode 100644 index e9687da443f9a..0000000000000 --- a/x-pack/legacy/plugins/code/server/utils/server_logger_factory.ts +++ /dev/null @@ -1,17 +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 { LoggerFactory } from 'src/core/server'; -import { Logger } from '../log'; -import { LoggerFactory as CodeLoggerFactory } from './log_factory'; - -export class ServerLoggerFactory implements CodeLoggerFactory { - constructor(private readonly loggerFactory: LoggerFactory, private readonly verbose: boolean) {} - - public getLogger(tags: string[] = []): Logger { - return new Logger(this.loggerFactory, this.verbose, tags); - } -} diff --git a/x-pack/legacy/plugins/code/server/utils/timeout.ts b/x-pack/legacy/plugins/code/server/utils/timeout.ts deleted file mode 100644 index 4892e082ee4bd..0000000000000 --- a/x-pack/legacy/plugins/code/server/utils/timeout.ts +++ /dev/null @@ -1,27 +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 Boom from 'boom'; - -export function promiseTimeout(ms: number, promise: Promise): Promise { - const boom = Boom.gatewayTimeout('Timed out in ' + ms + 'ms.'); - // @ts-ignore - boom.isTimeout = true; - - if (ms > 0) { - // Create a promise that rejects in milliseconds - const timeout = new Promise((resolve, reject) => { - const id = setTimeout(() => { - clearTimeout(id); - reject(boom); - }, ms); - }); - - // Returns a race between our timeout and the passed in promise - return Promise.race([promise, timeout]); - } else { - return Promise.reject(boom); - } -} diff --git a/x-pack/legacy/plugins/code/server/utils/with_internal_request.ts b/x-pack/legacy/plugins/code/server/utils/with_internal_request.ts deleted file mode 100644 index 9f8dde129039a..0000000000000 --- a/x-pack/legacy/plugins/code/server/utils/with_internal_request.ts +++ /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. - */ - -import { APICaller, IClusterClient } from 'src/core/server'; - -export class WithInternalRequest { - public readonly callCluster: APICaller; - - constructor(cluster: IClusterClient) { - this.callCluster = cluster.callAsInternalUser; - } -} diff --git a/x-pack/legacy/plugins/code/server/utils/with_request.ts b/x-pack/legacy/plugins/code/server/utils/with_request.ts deleted file mode 100644 index 167e9c41192f5..0000000000000 --- a/x-pack/legacy/plugins/code/server/utils/with_request.ts +++ /dev/null @@ -1,23 +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 { APICaller, KibanaRequest, RequestHandlerContext } from 'src/core/server'; - -export class WithRequest { - public readonly callCluster: APICaller; - - constructor(public readonly context: RequestHandlerContext, public readonly req: KibanaRequest) { - const securityPlugin = context.code.legacy.securityPlugin; - const useRbac = - securityPlugin && - securityPlugin.authorization && - // @ts-ignore - securityPlugin.authorization.mode.useRbacForRequest(req); - this.callCluster = useRbac - ? context.core.elasticsearch.dataClient.callAsInternalUser - : context.core.elasticsearch.dataClient.callAsCurrentUser; - } -} diff --git a/x-pack/legacy/plugins/code/webpackShims/stats-lite.d.ts b/x-pack/legacy/plugins/code/webpackShims/stats-lite.d.ts deleted file mode 100644 index 60bee75396767..0000000000000 --- a/x-pack/legacy/plugins/code/webpackShims/stats-lite.d.ts +++ /dev/null @@ -1,19 +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 function histogram(vals: any, bins: any): any; -export function mean(vals: any): any; -export function median(vals: any): any; -export function mode(vals: any): any; -export function numbers(vals: any): any; -export function percentile(vals: any, ptile: any): any; -export function populationStdev(vals: any): any; -export function populationVariance(vals: any): any; -export function sampleStdev(vals: any): any; -export function sampleVariance(vals: any): any; -export function stdev(vals: any): any; -export function sum(vals: any): any; -export function variance(vals: any): any; diff --git a/x-pack/legacy/plugins/encrypted_saved_objects/server/lib/encrypted_saved_objects_client_wrapper.test.ts b/x-pack/legacy/plugins/encrypted_saved_objects/server/lib/encrypted_saved_objects_client_wrapper.test.ts index 703ba64b95a7c..a9f41513fbf14 100644 --- a/x-pack/legacy/plugins/encrypted_saved_objects/server/lib/encrypted_saved_objects_client_wrapper.test.ts +++ b/x-pack/legacy/plugins/encrypted_saved_objects/server/lib/encrypted_saved_objects_client_wrapper.test.ts @@ -9,14 +9,14 @@ jest.mock('uuid', () => ({ v4: jest.fn().mockReturnValue('uuid-v4-id') })); import { EncryptedSavedObjectsClientWrapper } from './encrypted_saved_objects_client_wrapper'; import { EncryptedSavedObjectsService } from './encrypted_saved_objects_service'; import { createEncryptedSavedObjectsServiceMock } from './encrypted_saved_objects_service.mock'; -import { SavedObjectsClientMock } from 'src/core/server/saved_objects/service/saved_objects_client.mock'; +import { savedObjectsClientMock } from 'src/core/server/saved_objects/service/saved_objects_client.mock'; import { SavedObjectsClientContract } from 'src/core/server'; let wrapper: EncryptedSavedObjectsClientWrapper; let mockBaseClient: jest.Mocked; let encryptedSavedObjectsServiceMock: jest.Mocked; beforeEach(() => { - mockBaseClient = SavedObjectsClientMock.create(); + mockBaseClient = savedObjectsClientMock.create(); encryptedSavedObjectsServiceMock = createEncryptedSavedObjectsServiceMock([ { type: 'known-type', diff --git a/x-pack/legacy/plugins/graph/index.js b/x-pack/legacy/plugins/graph/index.js index 1a61caca7a7c1..9ece9966b7da4 100644 --- a/x-pack/legacy/plugins/graph/index.js +++ b/x-pack/legacy/plugins/graph/index.js @@ -23,7 +23,7 @@ export function graph(kibana) { order: 9000, icon: 'plugins/graph/icon.png', euiIconType: 'graphApp', - main: 'plugins/graph/app', + main: 'plugins/graph/index', }, styleSheetPaths: resolve(__dirname, 'public/index.scss'), hacks: ['plugins/graph/hacks/toggle_app_link_in_nav'], diff --git a/x-pack/legacy/plugins/graph/public/angular/directives/graph_inspect.js b/x-pack/legacy/plugins/graph/public/angular/directives/graph_inspect.js deleted file mode 100644 index 1e6622fd796fb..0000000000000 --- a/x-pack/legacy/plugins/graph/public/angular/directives/graph_inspect.js +++ /dev/null @@ -1,17 +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 { uiModules } from 'ui/modules'; -import template from '../templates/inspect.html'; -const app = uiModules.get('app/graph'); - -app.directive('graphInspect', function () { - return { - replace: true, - restrict: 'E', - template, - }; -}); diff --git a/x-pack/legacy/plugins/graph/public/angular/services/saved_workspace.js b/x-pack/legacy/plugins/graph/public/angular/services/saved_workspace.js index 1d28b4ba5ab30..444e68dd03520 100644 --- a/x-pack/legacy/plugins/graph/public/angular/services/saved_workspace.js +++ b/x-pack/legacy/plugins/graph/public/angular/services/saved_workspace.js @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { uiModules } from 'ui/modules'; import { SavedObjectProvider } from 'ui/saved_objects/saved_object'; import { i18n } from '@kbn/i18n'; import { @@ -12,8 +11,6 @@ import { injectReferences, } from './saved_workspace_references'; -const module = uiModules.get('app/dashboard'); - export function SavedWorkspaceProvider(Private) { // SavedWorkspace constructor. Usually you'd interact with an instance of this. // ID is option, without it one will be generated on save. @@ -68,8 +65,3 @@ export function SavedWorkspaceProvider(Private) { SavedWorkspace.searchsource = false; return SavedWorkspace; } - -// Used only by the savedDashboards service, usually no reason to change this -module.factory('SavedGraphWorkspace', function (Private) { - return Private(SavedWorkspaceProvider); -}); diff --git a/x-pack/legacy/plugins/graph/public/angular/services/saved_workspaces.js b/x-pack/legacy/plugins/graph/public/angular/services/saved_workspaces.js index 7af0dad3c704c..1fef4b7c38c07 100644 --- a/x-pack/legacy/plugins/graph/public/angular/services/saved_workspaces.js +++ b/x-pack/legacy/plugins/graph/public/angular/services/saved_workspaces.js @@ -6,7 +6,6 @@ import _ from 'lodash'; -import { uiModules } from 'ui/modules'; import chrome from 'ui/chrome'; import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry'; import { SavedObjectsClientProvider } from 'ui/saved_objects'; @@ -87,9 +86,5 @@ export function SavedWorkspacesProvider(kbnUrl, Private, Promise) { }); }; } -// This is the only thing that gets injected into controllers -uiModules.get('app/graph').service('savedGraphWorkspaces', function (Private) { - return Private(SavedWorkspacesProvider); -}); SavedObjectRegistryProvider.register(SavedWorkspacesProvider); diff --git a/x-pack/legacy/plugins/graph/public/angular/templates/index.html b/x-pack/legacy/plugins/graph/public/angular/templates/index.html index a3b025ff9d6df..686f0f590a19c 100644 --- a/x-pack/legacy/plugins/graph/public/angular/templates/index.html +++ b/x-pack/legacy/plugins/graph/public/angular/templates/index.html @@ -4,7 +4,49 @@

-
- -
- http://host:port/{{ selectedIndex.name }}/_graph/explore - - -
-
-
-
diff --git a/x-pack/legacy/plugins/graph/public/app.js b/x-pack/legacy/plugins/graph/public/app.js index ba69756e7b070..41e5819bcbf37 100644 --- a/x-pack/legacy/plugins/graph/public/app.js +++ b/x-pack/legacy/plugins/graph/public/app.js @@ -11,31 +11,10 @@ import React from 'react'; import { Provider } from 'react-redux'; import { isColorDark, hexToRgb } from '@elastic/eui'; -// import the uiExports that we want to "use" -import 'uiExports/fieldFormats'; -import 'uiExports/savedObjectTypes'; - -import 'ui/autoload/all'; -import 'ui/angular-bootstrap'; -import 'ui/kbn_top_nav'; -import 'ui/directives/saved_object_finder'; -import 'ui/directives/input_focus'; -import 'ui/saved_objects/ui/saved_object_save_as_checkbox'; -import 'uiExports/autocompleteProviders'; -import chrome from 'ui/chrome'; -import { uiModules } from 'ui/modules'; -import uiRoutes from 'ui/routes'; -import { addAppRedirectMessageToUrl, toastNotifications } from 'ui/notify'; -import { formatAngularHttpError } from 'ui/notify/lib'; -import { start as data } from '../../../../../src/legacy/core_plugins/data/public/legacy'; -import { SavedObjectsClientProvider } from 'ui/saved_objects'; -import { npStart } from 'ui/new_platform'; -import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry'; -import { capabilities } from 'ui/capabilities'; +import { KibanaParsedUrl } from 'ui/url/kibana_parsed_url'; import { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal'; -import { Storage } from 'ui/storage'; - -import { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; +import { formatAngularHttpError } from 'ui/notify/lib'; +import { addAppRedirectMessageToUrl } from 'ui/notify'; import appTemplate from './angular/templates/index.html'; import listingTemplate from './angular/templates/listing_ng_wrapper.html'; @@ -48,7 +27,6 @@ import { Settings } from './components/settings'; import { GraphVisualization } from './components/graph_visualization'; import gws from './angular/graph_client_workspace.js'; -import { SavedWorkspacesProvider } from './angular/services/saved_workspaces'; import { getEditUrl, getNewPath, getEditPath, setBreadcrumbs } from './services/url'; import { createCachedIndexPatternProvider } from './services/index_pattern_cache'; import { urlTemplateRegex } from './helpers/url_template'; @@ -62,528 +40,528 @@ import { hasFieldsSelector } from './state_management'; -import './angular/directives/graph_inspect'; +export function initGraphApp(angularModule, deps) { + const { + xpackInfo, + chrome, + savedGraphWorkspaces, + toastNotifications, + savedObjectsClient, + indexPatterns, + kbnBaseUrl, + addBasePath, + getBasePath, + npData, + config, + savedObjectRegistry, + capabilities, + coreStart, + $http, + Storage, + canEditDrillDownUrls, + graphSavePolicy, + } = deps; + + const app = angularModule; + + function checkLicense(kbnBaseUrl) { + const licenseAllowsToShowThisPage = xpackInfo.get('features.graph.showAppLink') && + xpackInfo.get('features.graph.enableAppLink'); + if (!licenseAllowsToShowThisPage) { + const message = xpackInfo.get('features.graph.message'); + const newUrl = addAppRedirectMessageToUrl(addBasePath(kbnBaseUrl), message); + window.location.href = newUrl; + throw new Error('Graph license error'); + } + } -const app = uiModules.get('app/graph'); + app.directive('vennDiagram', function (reactDirective) { + return reactDirective(VennDiagram); + }); -function checkLicense(kbnBaseUrl) { - const licenseAllowsToShowThisPage = xpackInfo.get('features.graph.showAppLink') && - xpackInfo.get('features.graph.enableAppLink'); - if (!licenseAllowsToShowThisPage) { - const message = xpackInfo.get('features.graph.message'); - const newUrl = addAppRedirectMessageToUrl(chrome.addBasePath(kbnBaseUrl), message); - window.location.href = newUrl; - throw new Error('Graph license error'); - } -} + app.directive('graphVisualization', function (reactDirective) { + return reactDirective(GraphVisualization); + }); -app.directive('vennDiagram', function (reactDirective) { - return reactDirective(VennDiagram); -}); - -app.directive('graphListing', function (reactDirective) { - return reactDirective(Listing, [ - ['coreStart', { watchDepth: 'reference' }], - ['createItem', { watchDepth: 'reference' }], - ['findItems', { watchDepth: 'reference' }], - ['deleteItems', { watchDepth: 'reference' }], - ['editItem', { watchDepth: 'reference' }], - ['getViewUrl', { watchDepth: 'reference' }], - ['listingLimit', { watchDepth: 'reference' }], - ['hideWriteControls', { watchDepth: 'reference' }], - ['capabilities', { watchDepth: 'reference' }], - ['initialFilter', { watchDepth: 'reference' }], - ]); -}); - -app.directive('graphApp', function (reactDirective) { - return reactDirective(GraphApp, [ - ['store', { watchDepth: 'reference' }], - ['isInitialized', { watchDepth: 'reference' }], - ['currentIndexPattern', { watchDepth: 'reference' }], - ['indexPatternProvider', { watchDepth: 'reference' }], - ['isLoading', { watchDepth: 'reference' }], - ['onQuerySubmit', { watchDepth: 'reference' }], - ['initialQuery', { watchDepth: 'reference' }], - ['confirmWipeWorkspace', { watchDepth: 'reference' }], - ['coreStart', { watchDepth: 'reference' }], - ['noIndexPatterns', { watchDepth: 'reference' }], - ['reduxStore', { watchDepth: 'reference' }], - ['pluginDataStart', { watchDepth: 'reference' }], - ], { restrict: 'A' }); -}); - -app.directive('graphVisualization', function (reactDirective) { - return reactDirective(GraphVisualization, undefined, { restrict: 'A' }); -}); - -if (uiRoutes.enable) { - uiRoutes.enable(); -} + app.directive('graphListing', function (reactDirective) { + return reactDirective(Listing, [ + ['coreStart', { watchDepth: 'reference' }], + ['createItem', { watchDepth: 'reference' }], + ['findItems', { watchDepth: 'reference' }], + ['deleteItems', { watchDepth: 'reference' }], + ['editItem', { watchDepth: 'reference' }], + ['getViewUrl', { watchDepth: 'reference' }], + ['listingLimit', { watchDepth: 'reference' }], + ['hideWriteControls', { watchDepth: 'reference' }], + ['capabilities', { watchDepth: 'reference' }], + ['initialFilter', { watchDepth: 'reference' }], + ]); + }); -uiRoutes - .when('/home', { - template: listingTemplate, - badge: getReadonlyBadge, - controller($injector, $location, $scope, Private, config, kbnBaseUrl) { - checkLicense(kbnBaseUrl); - const services = Private(SavedObjectRegistryProvider).byLoaderPropertiesName; - const graphService = services['Graph workspace']; - const kbnUrl = $injector.get('kbnUrl'); + app.directive('graphApp', function (reactDirective) { + return reactDirective(GraphApp, [ + ['store', { watchDepth: 'reference' }], + ['isInitialized', { watchDepth: 'reference' }], + ['currentIndexPattern', { watchDepth: 'reference' }], + ['indexPatternProvider', { watchDepth: 'reference' }], + ['isLoading', { watchDepth: 'reference' }], + ['onQuerySubmit', { watchDepth: 'reference' }], + ['initialQuery', { watchDepth: 'reference' }], + ['confirmWipeWorkspace', { watchDepth: 'reference' }], + ['coreStart', { watchDepth: 'reference' }], + ['noIndexPatterns', { watchDepth: 'reference' }], + ['reduxStore', { watchDepth: 'reference' }], + ['pluginDataStart', { watchDepth: 'reference' }], + ], { restrict: 'A' }); + }); - $scope.listingLimit = config.get('savedObjects:listingLimit'); - $scope.create = () => { - kbnUrl.redirect(getNewPath()); - }; - $scope.find = (search) => { - return graphService.find(search, $scope.listingLimit); - }; - $scope.editItem = (workspace) => { - kbnUrl.redirect(getEditPath(workspace)); - }; - $scope.getViewUrl = (workspace) => getEditUrl(chrome, workspace); - $scope.delete = (workspaces) => { - return graphService.delete(workspaces.map(({ id }) => id)); - }; - $scope.capabilities = capabilities.get().graph; - $scope.initialFilter = ($location.search()).filter || ''; - $scope.coreStart = npStart.core; - setBreadcrumbs({ chrome }); - } - }) - .when('/workspace/:id?', { - template: appTemplate, - badge: getReadonlyBadge, - resolve: { - savedWorkspace: function (savedGraphWorkspaces, $route) { - return $route.current.params.id ? savedGraphWorkspaces.get($route.current.params.id) - .catch( - function () { - toastNotifications.addDanger( - i18n.translate('xpack.graph.missingWorkspaceErrorMessage', { - defaultMessage: 'Missing workspace', - }) - ); - } - ) : savedGraphWorkspaces.get(); - }, - indexPatterns: function (Private) { - const savedObjectsClient = Private(SavedObjectsClientProvider); - - return savedObjectsClient.find({ - type: 'index-pattern', - fields: ['title', 'type'], - perPage: 10000 - }).then(response => response.savedObjects); - }, - GetIndexPatternProvider: function (Private) { - return data.indexPatterns.indexPatterns; - }, - SavedWorkspacesProvider: function (Private) { - return Private(SavedWorkspacesProvider); - } - } - }) - .otherwise({ - redirectTo: '/home' + app.directive('graphVisualization', function (reactDirective) { + return reactDirective(GraphVisualization, undefined, { restrict: 'A' }); }); + app.config(function ($routeProvider) { + $routeProvider.when('/home', { + template: listingTemplate, + badge: getReadonlyBadge, + controller($location, $scope) { + checkLicense(kbnBaseUrl); + const services = savedObjectRegistry.byLoaderPropertiesName; + const graphService = services['Graph workspace']; + + $scope.listingLimit = config.get('savedObjects:listingLimit'); + $scope.create = () => { + $location.url(getNewPath()); + }; + $scope.find = (search) => { + return graphService.find(search, $scope.listingLimit); + }; + $scope.editItem = (workspace) => { + $location.url(getEditPath(workspace)); + }; + $scope.getViewUrl = (workspace) => getEditUrl(addBasePath, workspace); + $scope.delete = (workspaces) => { + return graphService.delete(workspaces.map(({ id }) => id)); + }; + $scope.capabilities = capabilities; + $scope.initialFilter = ($location.search()).filter || ''; + $scope.coreStart = coreStart; + setBreadcrumbs({ chrome }); + } + }) + .when('/workspace/:id?', { + template: appTemplate, + badge: getReadonlyBadge, + resolve: { + savedWorkspace: function ($route) { + return $route.current.params.id ? savedGraphWorkspaces.get($route.current.params.id) + .catch( + function () { + toastNotifications.addDanger( + i18n.translate('xpack.graph.missingWorkspaceErrorMessage', { + defaultMessage: 'Missing workspace', + }) + ); + } + ) : savedGraphWorkspaces.get(); + + }, + //Copied from example found in wizard.js ( Kibana TODO - can't + indexPatterns: function () { + return savedObjectsClient.find({ + type: 'index-pattern', + fields: ['title', 'type'], + perPage: 10000 + }).then(response => response.savedObjects); + }, + GetIndexPatternProvider: function () { + return indexPatterns; + }, + } + }) + .otherwise({ + redirectTo: '/home' + }); + }); -//======== Controller for basic UI ================== -app.controller('graphuiPlugin', function ( - $scope, - $route, - $http, - kbnUrl, - confirmModal, - kbnBaseUrl -) { - checkLicense(kbnBaseUrl); - function handleError(err) { + //======== Controller for basic UI ================== + app.controller('graphuiPlugin', function ($scope, $route, $location, confirmModal) { checkLicense(kbnBaseUrl); - const toastTitle = i18n.translate('xpack.graph.errorToastTitle', { - defaultMessage: 'Graph Error', - description: '"Graph" is a product name and should not be translated.', - }); - if (err instanceof Error) { - toastNotifications.addError(err, { - title: toastTitle, - }); - } else { - toastNotifications.addDanger({ - title: toastTitle, - text: String(err), + + function handleError(err) { + checkLicense(kbnBaseUrl); + const toastTitle = i18n.translate('xpack.graph.errorToastTitle', { + defaultMessage: 'Graph Error', + description: '"Graph" is a product name and should not be translated.', }); + if (err instanceof Error) { + toastNotifications.addError(err, { + title: toastTitle, + }); + } else { + toastNotifications.addDanger({ + title: toastTitle, + text: String(err), + }); + } } - } - async function handleHttpError(error) { - checkLicense(kbnBaseUrl); - toastNotifications.addDanger(formatAngularHttpError(error)); - } + async function handleHttpError(error) { + checkLicense(kbnBaseUrl); + toastNotifications.addDanger(formatAngularHttpError(error)); + } - // Replacement function for graphClientWorkspace's comms so - // that it works with Kibana. - function callNodeProxy(indexName, query, responseHandler) { - const request = { - index: indexName, - query: query - }; - $scope.loading = true; - return $http.post('../api/graph/graphExplore', request) - .then(function (resp) { - if (resp.data.resp.timed_out) { - toastNotifications.addWarning( - i18n.translate('xpack.graph.exploreGraph.timedOutWarningText', { - defaultMessage: 'Exploration timed out', - }) - ); - } - responseHandler(resp.data.resp); - }) - .catch(handleHttpError) - .finally(() => { - $scope.loading = false; - }); - } + // Replacement function for graphClientWorkspace's comms so + // that it works with Kibana. + function callNodeProxy(indexName, query, responseHandler) { + const request = { + index: indexName, + query: query + }; + $scope.loading = true; + return $http.post('../api/graph/graphExplore', request) + .then(function (resp) { + if (resp.data.resp.timed_out) { + toastNotifications.addWarning( + i18n.translate('xpack.graph.exploreGraph.timedOutWarningText', { + defaultMessage: 'Exploration timed out', + }) + ); + } + responseHandler(resp.data.resp); + }) + .catch(handleHttpError) + .finally(() => { + $scope.loading = false; + }); + } - //Helper function for the graphClientWorkspace to perform a query - const callSearchNodeProxy = function (indexName, query, responseHandler) { - const request = { - index: indexName, - body: query - }; - $scope.loading = true; - $http.post('../api/graph/searchProxy', request) - .then(function (resp) { - responseHandler(resp.data.resp); - }) - .catch(handleHttpError) - .finally(() => { - $scope.loading = false; - }); - }; - - $scope.indexPatternProvider = createCachedIndexPatternProvider($route.current.locals.GetIndexPatternProvider.get); - - const store = createGraphStore({ - basePath: chrome.getBasePath(), - indexPatternProvider: $scope.indexPatternProvider, - indexPatterns: $route.current.locals.indexPatterns, - createWorkspace: (indexPattern, exploreControls) => { - const options = { - indexName: indexPattern, - vertex_fields: [], - // Here we have the opportunity to look up labels for nodes... - nodeLabeller: function () { - // console.log(newNodes); - }, - changeHandler: function () { - //Allows DOM to update with graph layout changes. - $scope.$apply(); - }, - graphExploreProxy: callNodeProxy, - searchProxy: callSearchNodeProxy, - exploreControls, + //Helper function for the graphClientWorkspace to perform a query + const callSearchNodeProxy = function (indexName, query, responseHandler) { + const request = { + index: indexName, + body: query }; - $scope.workspace = gws.createWorkspace(options); - }, - setLiveResponseFields: (fields) => { - $scope.liveResponseFields = fields; - }, - setUrlTemplates: (urlTemplates) => { - $scope.urlTemplates = urlTemplates; - }, - getWorkspace: () => { - return $scope.workspace; - }, - getSavedWorkspace: () => { - return $route.current.locals.savedWorkspace; - }, - notifications: npStart.core.notifications, - http: npStart.core.http, - showSaveModal, - setWorkspaceInitialized: () => { - $scope.workspaceInitialized = true; - }, - savePolicy: chrome.getInjected('graphSavePolicy'), - changeUrl: (newUrl) => { - $scope.$evalAsync(() => { - kbnUrl.change(newUrl, {}); - }); - }, - notifyAngular: () => { - $scope.$digest(); - }, - chrome, - }); + $scope.loading = true; + $http.post('../api/graph/searchProxy', request) + .then(function (resp) { + responseHandler(resp.data.resp); + }) + .catch(handleHttpError) + .finally(() => { + $scope.loading = false; + }); + }; - // register things on scope passed down to react components - $scope.pluginDataStart = npStart.plugins.data; - $scope.store = new Storage(window.localStorage); - $scope.coreStart = npStart.core; - $scope.loading = false; - $scope.reduxStore = store; - $scope.savedWorkspace = $route.current.locals.savedWorkspace; - - // register things for legacy angular UI - const allSavingDisabled = chrome.getInjected('graphSavePolicy') === 'none'; - $scope.spymode = 'request'; - $scope.colors = colorChoices; - $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) - if (!$event.shiftKey) { - const prevSelection = n.isSelected; - $scope.workspace.selectNone(); - n.isSelected = prevSelection; - } + $scope.indexPatternProvider = createCachedIndexPatternProvider($route.current.locals.GetIndexPatternProvider.get); + + const store = createGraphStore({ + basePath: getBasePath(), + indexPatternProvider: $scope.indexPatternProvider, + indexPatterns: $route.current.locals.indexPatterns, + createWorkspace: (indexPattern, exploreControls) => { + const options = { + indexName: indexPattern, + vertex_fields: [], + // Here we have the opportunity to look up labels for nodes... + nodeLabeller: function () { + // console.log(newNodes); + }, + changeHandler: function () { + //Allows DOM to update with graph layout changes. + $scope.$apply(); + }, + graphExploreProxy: callNodeProxy, + searchProxy: callSearchNodeProxy, + exploreControls, + }; + $scope.workspace = gws.createWorkspace(options); + }, + setLiveResponseFields: (fields) => { + $scope.liveResponseFields = fields; + }, + setUrlTemplates: (urlTemplates) => { + $scope.urlTemplates = urlTemplates; + }, + getWorkspace: () => { + return $scope.workspace; + }, + getSavedWorkspace: () => { + return $route.current.locals.savedWorkspace; + }, + notifications: coreStart.notifications, + http: coreStart.http, + showSaveModal, + setWorkspaceInitialized: () => { + $scope.workspaceInitialized = true; + }, + savePolicy: graphSavePolicy, + changeUrl: (newUrl) => { + $scope.$evalAsync(() => { + $location.url(newUrl); + }); + }, + notifyAngular: () => { + $scope.$digest(); + }, + chrome, + }); + // register things on scope passed down to react components + $scope.pluginDataStart = npData; + $scope.store = new Storage(window.localStorage); + $scope.coreStart = coreStart; + $scope.loading = false; + $scope.reduxStore = store; + $scope.savedWorkspace = $route.current.locals.savedWorkspace; + + // register things for legacy angular UI + const allSavingDisabled = graphSavePolicy === 'none'; + $scope.spymode = 'request'; + $scope.colors = colorChoices; + $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) + if (!$event.shiftKey) { + const prevSelection = n.isSelected; + $scope.workspace.selectNone(); + n.isSelected = prevSelection; + } - if ($scope.workspace.toggleNodeSelection(n)) { - $scope.selectSelected(n); - } else { - $scope.detail = null; - } - }; - function canWipeWorkspace(callback, text, options) { - if (!hasFieldsSelector(store.getState())) { - callback(); - return; - } - const confirmModalOptions = { - onConfirm: callback, - onCancel: (() => {}), - confirmButtonText: i18n.translate('xpack.graph.leaveWorkspace.confirmButtonLabel', { - defaultMessage: 'Leave anyway', - }), - title: i18n.translate('xpack.graph.leaveWorkspace.modalTitle', { - defaultMessage: 'Unsaved changes', - }), - ...options, + if ($scope.workspace.toggleNodeSelection(n)) { + $scope.selectSelected(n); + } else { + $scope.detail = null; + } }; - confirmModal(text || i18n.translate('xpack.graph.leaveWorkspace.confirmText', { - defaultMessage: 'If you leave now, you will lose unsaved changes.', - }), confirmModalOptions); - } - $scope.confirmWipeWorkspace = canWipeWorkspace; - $scope.clickEdge = function (edge) { - if (edge.inferred) { - $scope.setDetail ({ 'inferredEdge': edge }); - }else { - $scope.workspace.getAllIntersections($scope.handleMergeCandidatesCallback, [edge.topSrc, edge.topTarget]); - } - }; - - $scope.submit = function (searchTerm) { - $scope.workspaceInitialized = true; - const numHops = 2; - if (searchTerm.startsWith('{')) { - try { - const query = JSON.parse(searchTerm); - if (query.vertices) { - // Is a graph explore request - $scope.workspace.callElasticsearch(query); - }else { - // Is a regular query DSL query - $scope.workspace.search(query, $scope.liveResponseFields, numHops); - } + $scope.clickEdge = function (edge) { + if (edge.inferred) { + $scope.setDetail ({ 'inferredEdge': edge }); + }else { + $scope.workspace.getAllIntersections($scope.handleMergeCandidatesCallback, [edge.topSrc, edge.topTarget]); } - catch (err) { - handleError(err); + }; + + $scope.submit = function (searchTerm) { + $scope.workspaceInitialized = true; + const numHops = 2; + if (searchTerm.startsWith('{')) { + try { + const query = JSON.parse(searchTerm); + if (query.vertices) { + // Is a graph explore request + $scope.workspace.callElasticsearch(query); + }else { + // Is a regular query DSL query + $scope.workspace.search(query, $scope.liveResponseFields, numHops); + } + } + catch (err) { + handleError(err); + } + return; } - return; - } - $scope.workspace.simpleSearch(searchTerm, $scope.liveResponseFields, numHops); - }; + $scope.workspace.simpleSearch(searchTerm, $scope.liveResponseFields, numHops); + }; - $scope.selectSelected = function (node) { - $scope.detail = { - latestNodeSelection: node + $scope.selectSelected = function (node) { + $scope.detail = { + latestNodeSelection: node + }; + return $scope.selectedSelectedVertex = node; }; - return $scope.selectedSelectedVertex = node; - }; - - $scope.isSelectedSelected = function (node) { - return $scope.selectedSelectedVertex === node; - }; - - $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) => { - editor.$blockScrolling = Infinity; - }; - - $scope.setDetail = function (data) { - $scope.detail = data; - }; - - $scope.performMerge = function (parentId, childId) { - let found = true; - while (found) { - found = false; - for (const i in $scope.detail.mergeCandidates) { - const mc = $scope.detail.mergeCandidates[i]; - if ((mc.id1 === childId) || (mc.id2 === childId)) { - $scope.detail.mergeCandidates.splice(i, 1); - found = true; - break; - } + + $scope.isSelectedSelected = function (node) { + return $scope.selectedSelectedVertex === node; + }; + + $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) => { + editor.$blockScrolling = Infinity; + }; + + $scope.setDetail = function (data) { + $scope.detail = data; + }; + + function canWipeWorkspace(callback, text, options) { + if (!hasFieldsSelector(store.getState())) { + callback(); + return; } + const confirmModalOptions = { + onConfirm: callback, + onCancel: (() => {}), + confirmButtonText: i18n.translate('xpack.graph.leaveWorkspace.confirmButtonLabel', { + defaultMessage: 'Leave anyway', + }), + title: i18n.translate('xpack.graph.leaveWorkspace.modalTitle', { + defaultMessage: 'Unsaved changes', + }), + ...options, + }; + confirmModal(text || i18n.translate('xpack.graph.leaveWorkspace.confirmText', { + defaultMessage: 'If you leave now, you will lose unsaved changes.', + }), confirmModalOptions); } - $scope.workspace.mergeIds(parentId, childId); - $scope.detail = null; - }; - - - $scope.handleMergeCandidatesCallback = function (termIntersects) { - const mergeCandidates = []; - for (const i in termIntersects) { - const ti = termIntersects[i]; - mergeCandidates.push({ - 'id1': ti.id1, - 'id2': ti.id2, - 'term1': ti.term1, - 'term2': ti.term2, - 'v1': ti.v1, - 'v2': ti.v2, - 'overlap': ti.overlap - }); + $scope.confirmWipeWorkspace = canWipeWorkspace; + + $scope.performMerge = function (parentId, childId) { + let found = true; + while (found) { + found = false; + for (const i in $scope.detail.mergeCandidates) { + const mc = $scope.detail.mergeCandidates[i]; + if ((mc.id1 === childId) || (mc.id2 === childId)) { + $scope.detail.mergeCandidates.splice(i, 1); + found = true; + break; + } + } + } + $scope.workspace.mergeIds(parentId, childId); + $scope.detail = null; + }; - } - $scope.detail = { mergeCandidates }; - }; - - // ===== Menubar configuration ========= - $scope.topNavMenu = []; - $scope.topNavMenu.push({ - key: 'new', - label: i18n.translate('xpack.graph.topNavMenu.newWorkspaceLabel', { - defaultMessage: 'New', - }), - description: i18n.translate('xpack.graph.topNavMenu.newWorkspaceAriaLabel', { - defaultMessage: 'New Workspace', - }), - tooltip: i18n.translate('xpack.graph.topNavMenu.newWorkspaceTooltip', { - defaultMessage: 'Create a new workspace', - }), - run: function () { - canWipeWorkspace(function () { - kbnUrl.change('/workspace/', {}); - }); }, - testId: 'graphNewButton', - }); + $scope.handleMergeCandidatesCallback = function (termIntersects) { + const mergeCandidates = []; + for (const i in termIntersects) { + const ti = termIntersects[i]; + mergeCandidates.push({ + 'id1': ti.id1, + 'id2': ti.id2, + 'term1': ti.term1, + 'term2': ti.term2, + 'v1': ti.v1, + 'v2': ti.v2, + 'overlap': ti.overlap + }); - // if saving is disabled using uiCapabilities, we don't want to render the save - // button so it's consistent with all of the other applications - if (capabilities.get().graph.save) { - // allSavingDisabled is based on the xpack.graph.savePolicy, we'll maintain this functionality + } + $scope.detail = { mergeCandidates }; + }; + // ===== Menubar configuration ========= + $scope.topNavMenu = []; $scope.topNavMenu.push({ - key: 'save', - label: i18n.translate('xpack.graph.topNavMenu.saveWorkspace.enabledLabel', { - defaultMessage: 'Save', + key: 'new', + label: i18n.translate('xpack.graph.topNavMenu.newWorkspaceLabel', { + defaultMessage: 'New', }), - description: i18n.translate('xpack.graph.topNavMenu.saveWorkspace.enabledAriaLabel', { - defaultMessage: 'Save workspace', + description: i18n.translate('xpack.graph.topNavMenu.newWorkspaceAriaLabel', { + defaultMessage: 'New Workspace', }), - tooltip: () => { - if (allSavingDisabled) { - return i18n.translate('xpack.graph.topNavMenu.saveWorkspace.disabledTooltip', { - defaultMessage: 'No changes to saved workspaces are permitted by the current save policy', - }); - } else { - return i18n.translate('xpack.graph.topNavMenu.saveWorkspace.enabledTooltip', { - defaultMessage: 'Save this workspace', + tooltip: i18n.translate('xpack.graph.topNavMenu.newWorkspaceTooltip', { + defaultMessage: 'Create a new workspace', + }), + run: function () { + canWipeWorkspace(function () { + $scope.$evalAsync(() => { + if ($location.url() === '/workspace/') { + $route.reload(); + } else { + $location.url('/workspace/'); + } }); - } - }, - disableButton: function () { - return allSavingDisabled || !hasFieldsSelector(store.getState()); - }, - run: () => { - store.dispatch({ - type: 'x-pack/graph/SAVE_WORKSPACE', - payload: $route.current.locals.savedWorkspace, }); }, - testId: 'graphSaveButton', + testId: 'graphNewButton', }); - } - $scope.topNavMenu.push({ - key: 'inspect', - disableButton: function () { return $scope.workspace === null; }, - label: i18n.translate('xpack.graph.topNavMenu.inspectLabel', { - defaultMessage: 'Inspect', - }), - description: i18n.translate('xpack.graph.topNavMenu.inspectAriaLabel', { - defaultMessage: 'Inspect', - }), - run: () => { - $scope.$evalAsync(() => { - const curState = $scope.menus.showInspect; - $scope.closeMenus(); - $scope.menus.showInspect = !curState; - }); - }, - }); - $scope.topNavMenu.push({ - key: 'settings', - disableButton: function () { return datasourceSelector(store.getState()).type === 'none'; }, - label: i18n.translate('xpack.graph.topNavMenu.settingsLabel', { - defaultMessage: 'Settings', - }), - description: i18n.translate('xpack.graph.topNavMenu.settingsAriaLabel', { - defaultMessage: 'Settings', - }), - run: () => { - const settingsObservable = asAngularSyncedObservable(() => ({ - blacklistedNodes: $scope.workspace ? [...$scope.workspace.blacklistedNodes] : undefined, - unblacklistNode: $scope.workspace ? $scope.workspace.unblacklist : undefined, - canEditDrillDownUrls: chrome.getInjected('canEditDrillDownUrls') - }), $scope.$digest.bind($scope)); - npStart.core.overlays.openFlyout( - - - , { - size: 'm', - closeButtonAriaLabel: i18n.translate('xpack.graph.settings.closeLabel', { defaultMessage: 'Close' }), - 'data-test-subj': 'graphSettingsFlyout', - ownFocus: true, - className: 'gphSettingsFlyout', - maxWidth: 520, + // if saving is disabled using uiCapabilities, we don't want to render the save + // button so it's consistent with all of the other applications + if (capabilities.save) { + // allSavingDisabled is based on the xpack.graph.savePolicy, we'll maintain this functionality + + $scope.topNavMenu.push({ + key: 'save', + label: i18n.translate('xpack.graph.topNavMenu.saveWorkspace.enabledLabel', { + defaultMessage: 'Save', + }), + description: i18n.translate('xpack.graph.topNavMenu.saveWorkspace.enabledAriaLabel', { + defaultMessage: 'Save workspace', + }), + tooltip: () => { + if (allSavingDisabled) { + return i18n.translate('xpack.graph.topNavMenu.saveWorkspace.disabledTooltip', { + defaultMessage: 'No changes to saved workspaces are permitted by the current save policy', + }); + } else { + return i18n.translate('xpack.graph.topNavMenu.saveWorkspace.enabledTooltip', { + defaultMessage: 'Save this workspace', + }); + } + }, + disableButton: function () { + return allSavingDisabled || !hasFieldsSelector(store.getState()); + }, + run: () => { + store.dispatch({ + type: 'x-pack/graph/SAVE_WORKSPACE', + payload: $route.current.locals.savedWorkspace, + }); + }, + testId: 'graphSaveButton', + }); + } + $scope.topNavMenu.push({ + key: 'inspect', + disableButton: function () { return $scope.workspace === null; }, + label: i18n.translate('xpack.graph.topNavMenu.inspectLabel', { + defaultMessage: 'Inspect', + }), + description: i18n.translate('xpack.graph.topNavMenu.inspectAriaLabel', { + defaultMessage: 'Inspect', + }), + run: () => { + $scope.$evalAsync(() => { + const curState = $scope.menus.showInspect; + $scope.closeMenus(); + $scope.menus.showInspect = !curState; }); - }, - }); - - $scope.menus = { - showSettings: false, - }; - - $scope.closeMenus = () => { - _.forOwn($scope.menus, function (_, key) { - $scope.menus[key] = false; + }, }); - }; - // Deal with situation of request to open saved workspace - if ($route.current.locals.savedWorkspace.id) { - store.dispatch({ - type: 'x-pack/graph/LOAD_WORKSPACE', - payload: $route.current.locals.savedWorkspace, + $scope.topNavMenu.push({ + key: 'settings', + disableButton: function () { return datasourceSelector(store.getState()).type === 'none'; }, + label: i18n.translate('xpack.graph.topNavMenu.settingsLabel', { + defaultMessage: 'Settings', + }), + description: i18n.translate('xpack.graph.topNavMenu.settingsAriaLabel', { + defaultMessage: 'Settings', + }), + run: () => { + const settingsObservable = asAngularSyncedObservable(() => ({ + blacklistedNodes: $scope.workspace ? [...$scope.workspace.blacklistedNodes] : undefined, + unblacklistNode: $scope.workspace ? $scope.workspace.unblacklist : undefined, + canEditDrillDownUrls: canEditDrillDownUrls + }), $scope.$digest.bind($scope)); + coreStart.overlays.openFlyout( + + + , { + size: 'm', + closeButtonAriaLabel: i18n.translate('xpack.graph.settings.closeLabel', { defaultMessage: 'Close' }), + 'data-test-subj': 'graphSettingsFlyout', + ownFocus: true, + className: 'gphSettingsFlyout', + maxWidth: 520, + }); + }, }); + // Allow URLs to include a user-defined text query if ($route.current.params.query) { $scope.initialQuery = $route.current.params.query; @@ -595,8 +573,26 @@ app.controller('graphuiPlugin', function ( $scope.submit($route.current.params.query); }); } - } else { - $scope.noIndexPatterns = $route.current.locals.indexPatterns.length === 0; - } -}); + $scope.menus = { + showSettings: false, + }; + + $scope.closeMenus = () => { + _.forOwn($scope.menus, function (_, key) { + $scope.menus[key] = false; + }); + }; + + // Deal with situation of request to open saved workspace + if ($route.current.locals.savedWorkspace.id) { + store.dispatch({ + type: 'x-pack/graph/LOAD_WORKSPACE', + payload: $route.current.locals.savedWorkspace, + }); + } else { + $scope.noIndexPatterns = $route.current.locals.indexPatterns.length === 0; + } + }); +//End controller +} diff --git a/x-pack/legacy/plugins/graph/public/hacks/toggle_app_link_in_nav.js b/x-pack/legacy/plugins/graph/public/hacks/toggle_app_link_in_nav.js index 11b02354a97c5..5b1eb6181fd61 100644 --- a/x-pack/legacy/plugins/graph/public/hacks/toggle_app_link_in_nav.js +++ b/x-pack/legacy/plugins/graph/public/hacks/toggle_app_link_in_nav.js @@ -4,20 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import { uiModules } from 'ui/modules'; import { npStart } from 'ui/new_platform'; import { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; -uiModules.get('xpack/graph') - .run(() => { - const navLinkUpdates = {}; - navLinkUpdates.hidden = true; - const showAppLink = xpackInfo.get('features.graph.showAppLink', false); - navLinkUpdates.hidden = !showAppLink; - if (showAppLink) { - navLinkUpdates.disabled = !xpackInfo.get('features.graph.enableAppLink', false); - navLinkUpdates.tooltip = xpackInfo.get('features.graph.message'); - } +const navLinkUpdates = {}; +navLinkUpdates.hidden = true; +const showAppLink = xpackInfo.get('features.graph.showAppLink', false); +navLinkUpdates.hidden = !showAppLink; +if (showAppLink) { + navLinkUpdates.disabled = !xpackInfo.get('features.graph.enableAppLink', false); + navLinkUpdates.tooltip = xpackInfo.get('features.graph.message'); +} - npStart.core.chrome.navLinks.update('graph', navLinkUpdates); - }); +npStart.core.chrome.navLinks.update('graph', navLinkUpdates); diff --git a/x-pack/legacy/plugins/graph/public/index.ts b/x-pack/legacy/plugins/graph/public/index.ts new file mode 100644 index 0000000000000..0249ca74035d6 --- /dev/null +++ b/x-pack/legacy/plugins/graph/public/index.ts @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +// legacy imports currently necessary to power Graph +// for a cutover all of these have to be resolved +import 'uiExports/fieldFormats'; +import 'uiExports/savedObjectTypes'; +import 'uiExports/autocompleteProviders'; +import 'ui/autoload/all'; +import chrome from 'ui/chrome'; +import { IPrivate } from 'ui/private'; +// @ts-ignore +import { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; +import { Storage } from 'ui/storage'; +// @ts-ignore +import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry'; + +import { npSetup, npStart } from 'ui/new_platform'; +import { start as data } from '../../../../../src/legacy/core_plugins/data/public/legacy'; +import { GraphPlugin } from './plugin'; + +// @ts-ignore +import { SavedWorkspacesProvider } from './angular/services/saved_workspaces'; +import { LegacyAngularInjectedDependencies } from './render_app'; + +/** + * Get dependencies relying on the global angular context. + * They also have to get resolved together with the legacy imports above + */ +async function getAngularInjectedDependencies(): Promise { + const injector = await chrome.dangerouslyGetActiveInjector(); + + const Private = injector.get('Private'); + + return { + $http: injector.get('$http'), + savedObjectRegistry: Private(SavedObjectRegistryProvider), + kbnBaseUrl: injector.get('kbnBaseUrl'), + savedGraphWorkspaces: Private(SavedWorkspacesProvider), + }; +} + +(async () => { + const instance = new GraphPlugin(); + instance.setup(npSetup.core, { + __LEGACY: { + xpackInfo, + Storage, + }, + }); + instance.start(npStart.core, { + data, + npData: npStart.plugins.data, + __LEGACY: { + angularDependencies: await getAngularInjectedDependencies(), + }, + }); +})(); diff --git a/x-pack/legacy/plugins/graph/public/plugin.ts b/x-pack/legacy/plugins/graph/public/plugin.ts new file mode 100644 index 0000000000000..d01f325dd8b70 --- /dev/null +++ b/x-pack/legacy/plugins/graph/public/plugin.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. + */ + +// NP type imports +import { CoreSetup, CoreStart, Plugin, SavedObjectsClientContract } from 'src/core/public'; +import { DataStart } from 'src/legacy/core_plugins/data/public'; +import { Plugin as DataPlugin } from 'src/plugins/data/public'; +import { LegacyAngularInjectedDependencies } from './render_app'; + +export interface GraphPluginStartDependencies { + data: DataStart; + npData: ReturnType; +} + +export interface GraphPluginSetupDependencies { + __LEGACY: { + Storage: any; + xpackInfo: any; + }; +} + +export interface GraphPluginStartDependencies { + __LEGACY: { + angularDependencies: LegacyAngularInjectedDependencies; + }; +} + +export class GraphPlugin implements Plugin { + private dataStart: DataStart | null = null; + private npDataStart: ReturnType | null = null; + private savedObjectsClient: SavedObjectsClientContract | null = null; + private angularDependencies: LegacyAngularInjectedDependencies | null = null; + + setup(core: CoreSetup, { __LEGACY: { xpackInfo, Storage } }: GraphPluginSetupDependencies) { + core.application.register({ + id: 'graph', + title: 'Graph', + mount: async ({ core: contextCore }, params) => { + const { renderApp } = await import('./render_app'); + return renderApp({ + ...params, + npData: this.npDataStart!, + savedObjectsClient: this.savedObjectsClient!, + xpackInfo, + addBasePath: core.http.basePath.prepend, + getBasePath: core.http.basePath.get, + canEditDrillDownUrls: core.injectedMetadata.getInjectedVar( + 'canEditDrillDownUrls' + ) as boolean, + graphSavePolicy: core.injectedMetadata.getInjectedVar('graphSavePolicy') as string, + Storage, + capabilities: contextCore.application.capabilities.graph, + coreStart: contextCore, + chrome: contextCore.chrome, + config: contextCore.uiSettings, + toastNotifications: contextCore.notifications.toasts, + indexPatterns: this.dataStart!.indexPatterns.indexPatterns, + ...this.angularDependencies!, + }); + }, + }); + } + + start( + core: CoreStart, + { data, npData, __LEGACY: { angularDependencies } }: GraphPluginStartDependencies + ) { + // TODO is this really the right way? I though the app context would give us those + this.dataStart = data; + this.npDataStart = npData; + this.angularDependencies = angularDependencies; + this.savedObjectsClient = core.savedObjects.client; + } + + stop() {} +} diff --git a/x-pack/legacy/plugins/graph/public/render_app.ts b/x-pack/legacy/plugins/graph/public/render_app.ts new file mode 100644 index 0000000000000..8625e20ab9c52 --- /dev/null +++ b/x-pack/legacy/plugins/graph/public/render_app.ts @@ -0,0 +1,145 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiConfirmModal } from '@elastic/eui'; + +// inner angular imports +// these are necessary to bootstrap the local angular. +// They can stay even after NP cutover +import angular from 'angular'; +import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular'; +import 'ui/angular-bootstrap'; +import 'ace'; +import 'ui/kbn_top_nav'; +import { configureAppAngularModule } from 'ui/legacy_compat'; +// @ts-ignore +import { createTopNavDirective, createTopNavHelper } from 'ui/kbn_top_nav/kbn_top_nav'; +// @ts-ignore +import { confirmModalFactory } from 'ui/modals/confirm_modal'; + +// type imports +import { DataStart } from 'src/legacy/core_plugins/data/public'; +import { + AppMountContext, + ChromeStart, + SavedObjectsClientContract, + ToastsStart, + UiSettingsClientContract, +} from 'kibana/public'; +// @ts-ignore +import { initGraphApp } from './app'; +import { Plugin as DataPlugin } from '../../../../../src/plugins/data/public'; + +/** + * These are dependencies of the Graph app besides the base dependencies + * provided by the application service. Some of those still rely on non-shimmed + * plugins in LP-world, but if they are migrated only the import path in the plugin + * itself changes + */ +export interface GraphDependencies extends LegacyAngularInjectedDependencies { + element: HTMLElement; + appBasePath: string; + capabilities: Record>; + coreStart: AppMountContext['core']; + chrome: ChromeStart; + config: UiSettingsClientContract; + toastNotifications: ToastsStart; + indexPatterns: DataStart['indexPatterns']['indexPatterns']; + npData: ReturnType; + savedObjectsClient: SavedObjectsClientContract; + xpackInfo: { get(path: string): unknown }; + addBasePath: (url: string) => string; + getBasePath: () => string; + Storage: any; + canEditDrillDownUrls: boolean; + graphSavePolicy: string; +} + +/** + * Dependencies of the Graph app which rely on the global angular instance. + * These dependencies have to be migrated to their NP counterparts. + */ +export interface LegacyAngularInjectedDependencies { + /** + * angular $http service + */ + $http: any; + /** + * Instance of SavedObjectRegistryProvider + */ + savedObjectRegistry: any; + kbnBaseUrl: any; + /** + * Instance of SavedWorkspacesProvider + */ + savedGraphWorkspaces: any; +} + +export const renderApp = ({ appBasePath, element, ...deps }: GraphDependencies) => { + const graphAngularModule = createLocalAngularModule(deps.coreStart); + configureAppAngularModule(graphAngularModule); + initGraphApp(graphAngularModule, deps); + const $injector = mountGraphApp(appBasePath, element); + return () => $injector.get('$rootScope').$destroy(); +}; + +const mainTemplate = (basePath: string) => `
+ +
+
+`; + +const moduleName = 'app/graph'; + +const thirdPartyAngularDependencies = ['ngSanitize', 'ngRoute', 'react', 'ui.bootstrap', 'ui.ace']; + +function mountGraphApp(appBasePath: string, element: HTMLElement) { + const mountpoint = document.createElement('div'); + mountpoint.setAttribute('style', 'height: 100%'); + // eslint-disable-next-line + mountpoint.innerHTML = mainTemplate(appBasePath); + // bootstrap angular into detached element and attach it later to + // make angular-within-angular possible + const $injector = angular.bootstrap(mountpoint, [moduleName]); + element.appendChild(mountpoint); + return $injector; +} + +function createLocalAngularModule(core: AppMountContext['core']) { + createLocalI18nModule(); + createLocalTopNavModule(); + createLocalConfirmModalModule(); + + const graphAngularModule = angular.module(moduleName, [ + ...thirdPartyAngularDependencies, + 'graphI18n', + 'graphTopNav', + 'graphConfirmModal', + ]); + return graphAngularModule; +} + +function createLocalConfirmModalModule() { + angular + .module('graphConfirmModal', ['react']) + .factory('confirmModal', confirmModalFactory) + .directive('confirmModal', reactDirective => reactDirective(EuiConfirmModal)); +} + +function createLocalTopNavModule() { + angular + .module('graphTopNav', ['react']) + .directive('kbnTopNav', createTopNavDirective) + .directive('kbnTopNavHelper', createTopNavHelper); +} + +function createLocalI18nModule() { + angular + .module('graphI18n', []) + .provider('i18n', I18nProvider) + .filter('i18n', i18nFilter) + .directive('i18nId', i18nDirective); +} diff --git a/x-pack/legacy/plugins/graph/public/services/url.ts b/x-pack/legacy/plugins/graph/public/services/url.ts index 91e14564bf3aa..b709683a457fb 100644 --- a/x-pack/legacy/plugins/graph/public/services/url.ts +++ b/x-pack/legacy/plugins/graph/public/services/url.ts @@ -5,7 +5,7 @@ */ import { i18n } from '@kbn/i18n'; -import { Chrome } from 'ui/chrome'; +import { ChromeStart } from 'kibana/public'; import { GraphWorkspaceSavedObject } from '../types'; import { MetaDataState } from '../state_management'; @@ -21,23 +21,26 @@ export function getEditPath({ id }: GraphWorkspaceSavedObject) { return `/workspace/${id}`; } -export function getEditUrl(chrome: Chrome, workspace: GraphWorkspaceSavedObject) { - return chrome.addBasePath(`#${getEditPath(workspace)}`); +export function getEditUrl( + addBasePath: (url: string) => string, + workspace: GraphWorkspaceSavedObject +) { + return addBasePath(`#${getEditPath(workspace)}`); } export type SetBreadcrumbOptions = | { - chrome: Chrome; + chrome: ChromeStart; } | { - chrome: Chrome; + chrome: ChromeStart; metaData: MetaDataState; navigateTo: (path: string) => void; }; export function setBreadcrumbs(options: SetBreadcrumbOptions) { if ('metaData' in options) { - options.chrome.breadcrumbs.set([ + options.chrome.setBreadcrumbs([ { text: i18n.translate('xpack.graph.home.breadcrumb', { defaultMessage: 'Graph', @@ -53,7 +56,7 @@ export function setBreadcrumbs(options: SetBreadcrumbOptions) { }, ]); } else { - options.chrome.breadcrumbs.set([ + options.chrome.setBreadcrumbs([ { text: i18n.translate('xpack.graph.home.breadcrumb', { defaultMessage: 'Graph', diff --git a/x-pack/legacy/plugins/graph/public/state_management/meta_data.test.ts b/x-pack/legacy/plugins/graph/public/state_management/meta_data.test.ts index 8ac3746158f3f..25be050edfc13 100644 --- a/x-pack/legacy/plugins/graph/public/state_management/meta_data.test.ts +++ b/x-pack/legacy/plugins/graph/public/state_management/meta_data.test.ts @@ -6,7 +6,6 @@ import { createMockGraphStore, MockedGraphEnvironment } from './mocks'; import { syncBreadcrumbSaga, updateMetaData } from './meta_data'; -import { Chrome } from 'ui/chrome'; describe('breadcrumb sync saga', () => { let env: MockedGraphEnvironment; @@ -14,22 +13,15 @@ describe('breadcrumb sync saga', () => { beforeEach(() => { env = createMockGraphStore({ sagas: [syncBreadcrumbSaga], - mockedDepsOverwrites: { - chrome: ({ - breadcrumbs: { - set: jest.fn(), - }, - } as unknown) as Chrome, - }, }); }); it('syncs breadcrumb initially', () => { - expect(env.mockedDeps.chrome.breadcrumbs.set).toHaveBeenCalled(); + expect(env.mockedDeps.chrome.setBreadcrumbs).toHaveBeenCalled(); }); it('syncs breadcrumb with each change to meta data', () => { env.store.dispatch(updateMetaData({})); - expect(env.mockedDeps.chrome.breadcrumbs.set).toHaveBeenCalledTimes(2); + expect(env.mockedDeps.chrome.setBreadcrumbs).toHaveBeenCalledTimes(2); }); }); diff --git a/x-pack/legacy/plugins/graph/public/state_management/mocks.ts b/x-pack/legacy/plugins/graph/public/state_management/mocks.ts index 11cbf84e759ea..9672edef31d6f 100644 --- a/x-pack/legacy/plugins/graph/public/state_management/mocks.ts +++ b/x-pack/legacy/plugins/graph/public/state_management/mocks.ts @@ -4,11 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Chrome } from 'ui/chrome'; import { IndexPattern } from 'src/legacy/core_plugins/data/public'; import { NotificationsStart, HttpStart } from 'kibana/public'; import createSagaMiddleware from 'redux-saga'; import { createStore, applyMiddleware, AnyAction } from 'redux'; +import { ChromeStart } from 'kibana/public'; import { GraphStoreDependencies, createRootReducer, GraphStore, GraphState } from './store'; import { Workspace, GraphWorkspaceSavedObject, IndexPatternSavedObject } from '../types'; @@ -52,10 +52,8 @@ export function createMockGraphStore({ basePath: 'basepath', changeUrl: jest.fn(), chrome: ({ - breadcrumbs: { - set: jest.fn(), - }, - } as unknown) as Chrome, + setBreadcrumbs: jest.fn(), + } as unknown) as ChromeStart, createWorkspace: jest.fn(), getWorkspace: jest.fn(() => workspaceMock), getSavedWorkspace: jest.fn(() => savedWorkspace), diff --git a/x-pack/legacy/plugins/graph/public/state_management/store.ts b/x-pack/legacy/plugins/graph/public/state_management/store.ts index 752483e65d8cc..bb01f20196f87 100644 --- a/x-pack/legacy/plugins/graph/public/state_management/store.ts +++ b/x-pack/legacy/plugins/graph/public/state_management/store.ts @@ -6,8 +6,8 @@ import createSagaMiddleware, { SagaMiddleware } from 'redux-saga'; import { combineReducers, createStore, Store, AnyAction, Dispatch, applyMiddleware } from 'redux'; +import { ChromeStart } from 'kibana/public'; import { CoreStart } from 'src/core/public'; -import { Chrome } from 'ui/chrome'; import { fieldsReducer, FieldsState, @@ -61,7 +61,7 @@ export interface GraphStoreDependencies { setLiveResponseFields: (fields: WorkspaceField[]) => void; setUrlTemplates: (templates: UrlTemplate[]) => void; setWorkspaceInitialized: () => void; - chrome: Chrome; + chrome: ChromeStart; } export function createRootReducer(basePath: string) { diff --git a/x-pack/legacy/plugins/index_lifecycle_management/public/sections/edit_policy/components/cold_phase/cold_phase.js b/x-pack/legacy/plugins/index_lifecycle_management/public/sections/edit_policy/components/cold_phase/cold_phase.js index 493da3e19b9f3..91fbb4f63cbc3 100644 --- a/x-pack/legacy/plugins/index_lifecycle_management/public/sections/edit_policy/components/cold_phase/cold_phase.js +++ b/x-pack/legacy/plugins/index_lifecycle_management/public/sections/edit_policy/components/cold_phase/cold_phase.js @@ -58,12 +58,12 @@ export class ColdPhase extends PureComponent { - +

- {' '} +

{' '} {phaseData[PHASE_ENABLED] && !isShowingErrors ? : null}
@@ -162,12 +162,12 @@ export class ColdPhase extends PureComponent { +

-

+

} description={ diff --git a/x-pack/legacy/plugins/index_lifecycle_management/public/sections/edit_policy/components/delete_phase/delete_phase.js b/x-pack/legacy/plugins/index_lifecycle_management/public/sections/edit_policy/components/delete_phase/delete_phase.js index 0e7663a6aef7d..d5eb76889494c 100644 --- a/x-pack/legacy/plugins/index_lifecycle_management/public/sections/edit_policy/components/delete_phase/delete_phase.js +++ b/x-pack/legacy/plugins/index_lifecycle_management/public/sections/edit_policy/components/delete_phase/delete_phase.js @@ -43,12 +43,12 @@ export class DeletePhase extends PureComponent { - +

- {' '} +

{' '} {phaseData[PHASE_ENABLED] && !isShowingErrors ? ( ) : null} diff --git a/x-pack/legacy/plugins/index_lifecycle_management/public/sections/edit_policy/components/hot_phase/hot_phase.js b/x-pack/legacy/plugins/index_lifecycle_management/public/sections/edit_policy/components/hot_phase/hot_phase.js index 69370ab2d0a38..e7ac890a20ebc 100644 --- a/x-pack/legacy/plugins/index_lifecycle_management/public/sections/edit_policy/components/hot_phase/hot_phase.js +++ b/x-pack/legacy/plugins/index_lifecycle_management/public/sections/edit_policy/components/hot_phase/hot_phase.js @@ -54,12 +54,12 @@ export class HotPhase extends PureComponent { - +

- {' '} +

{' '} {isShowingErrors ? null : }
diff --git a/x-pack/legacy/plugins/index_lifecycle_management/public/sections/edit_policy/components/set_priority_input.js b/x-pack/legacy/plugins/index_lifecycle_management/public/sections/edit_policy/components/set_priority_input.js index 3f4c32b05a2dc..c2c2b002a7693 100644 --- a/x-pack/legacy/plugins/index_lifecycle_management/public/sections/edit_policy/components/set_priority_input.js +++ b/x-pack/legacy/plugins/index_lifecycle_management/public/sections/edit_policy/components/set_priority_input.js @@ -21,12 +21,12 @@ export const SetPriorityInput = props => { return ( +

-

+

} description={ diff --git a/x-pack/legacy/plugins/index_lifecycle_management/public/sections/edit_policy/components/warm_phase/warm_phase.js b/x-pack/legacy/plugins/index_lifecycle_management/public/sections/edit_policy/components/warm_phase/warm_phase.js index 2f88578e128ef..77afd4f7051dc 100644 --- a/x-pack/legacy/plugins/index_lifecycle_management/public/sections/edit_policy/components/warm_phase/warm_phase.js +++ b/x-pack/legacy/plugins/index_lifecycle_management/public/sections/edit_policy/components/warm_phase/warm_phase.js @@ -77,12 +77,12 @@ export class WarmPhase extends PureComponent { - +

- {' '} +

{' '} {phaseData[PHASE_ENABLED] && !isShowingErrors ? : null}
@@ -202,12 +202,12 @@ export class WarmPhase extends PureComponent { +

-

+

} description={ @@ -270,12 +270,12 @@ export class WarmPhase extends PureComponent {
+

-

+

} description={ diff --git a/x-pack/legacy/plugins/index_lifecycle_management/public/sections/edit_policy/edit_policy.js b/x-pack/legacy/plugins/index_lifecycle_management/public/sections/edit_policy/edit_policy.js index 2b07e9615030f..ef9b6de84f1e4 100644 --- a/x-pack/legacy/plugins/index_lifecycle_management/public/sections/edit_policy/edit_policy.js +++ b/x-pack/legacy/plugins/index_lifecycle_management/public/sections/edit_policy/edit_policy.js @@ -157,8 +157,8 @@ export class EditPolicy extends Component { verticalPosition="center" horizontalPosition="center" > - -

+ +

{isNewPolicy ? i18n.translate('xpack.indexLifecycleMgmt.editPolicy.createPolicyMessage', { defaultMessage: 'Create an index lifecycle policy' @@ -167,7 +167,7 @@ export class EditPolicy extends Component { defaultMessage: 'Edit index lifecycle policy {originalPolicyName}', values: { originalPolicyName } })} -

+

diff --git a/x-pack/legacy/plugins/index_management/public/components/template_form/steps/step_aliases.tsx b/x-pack/legacy/plugins/index_management/public/components/template_form/steps/step_aliases.tsx index 6c7a5a0ce922c..96abbae93d0f4 100644 --- a/x-pack/legacy/plugins/index_management/public/components/template_form/steps/step_aliases.tsx +++ b/x-pack/legacy/plugins/index_management/public/components/template_form/steps/step_aliases.tsx @@ -39,12 +39,12 @@ export const StepAliases: React.FunctionComponent = ({ -

+

-

+
diff --git a/x-pack/legacy/plugins/index_management/public/components/template_form/steps/step_logistics.tsx b/x-pack/legacy/plugins/index_management/public/components/template_form/steps/step_logistics.tsx index 1b20da3a0ee4c..86ef102dcec44 100644 --- a/x-pack/legacy/plugins/index_management/public/components/template_form/steps/step_logistics.tsx +++ b/x-pack/legacy/plugins/index_management/public/components/template_form/steps/step_logistics.tsx @@ -22,7 +22,7 @@ import { schemas } from '../template_form_schemas'; // Create or Form components with partial props that are common to all instances const UseField = getUseField({ component: Field }); -const FormRow = getFormRow({ titleTag: 'h4' }); +const FormRow = getFormRow({ titleTag: 'h3' }); const fieldsMeta = { name: { @@ -97,12 +97,12 @@ export const StepLogistics: React.FunctionComponent = ({ -

+

-

+
diff --git a/x-pack/legacy/plugins/index_management/public/components/template_form/steps/step_mappings.tsx b/x-pack/legacy/plugins/index_management/public/components/template_form/steps/step_mappings.tsx index 17d4a06418a70..74cb09b1c8e69 100644 --- a/x-pack/legacy/plugins/index_management/public/components/template_form/steps/step_mappings.tsx +++ b/x-pack/legacy/plugins/index_management/public/components/template_form/steps/step_mappings.tsx @@ -39,12 +39,12 @@ export const StepMappings: React.FunctionComponent = ({ -

+

-

+
diff --git a/x-pack/legacy/plugins/index_management/public/components/template_form/steps/step_review.tsx b/x-pack/legacy/plugins/index_management/public/components/template_form/steps/step_review.tsx index a639785c3837c..2c7fe939cc759 100644 --- a/x-pack/legacy/plugins/index_management/public/components/template_form/steps/step_review.tsx +++ b/x-pack/legacy/plugins/index_management/public/components/template_form/steps/step_review.tsx @@ -188,13 +188,13 @@ export const StepReview: React.FunctionComponent = ({ template, updat return (
-

+

-

+
diff --git a/x-pack/legacy/plugins/index_management/public/components/template_form/steps/step_settings.tsx b/x-pack/legacy/plugins/index_management/public/components/template_form/steps/step_settings.tsx index 850f312f0d1c6..9509ca38003ba 100644 --- a/x-pack/legacy/plugins/index_management/public/components/template_form/steps/step_settings.tsx +++ b/x-pack/legacy/plugins/index_management/public/components/template_form/steps/step_settings.tsx @@ -39,12 +39,12 @@ export const StepSettings: React.FunctionComponent = ({ -

+

-

+
diff --git a/x-pack/legacy/plugins/infra/index.ts b/x-pack/legacy/plugins/infra/index.ts index 671ae7d747e01..9bf679fb5ff80 100644 --- a/x-pack/legacy/plugins/infra/index.ts +++ b/x-pack/legacy/plugins/infra/index.ts @@ -43,7 +43,7 @@ export function infra(kibana: any) { defaultMessage: 'Explore your metrics', }), icon: 'plugins/infra/images/infra_mono_white.svg', - euiIconType: 'infraApp', + euiIconType: 'metricsApp', id: 'infra:home', order: 8000, title: i18n.translate('xpack.infra.linkInfrastructureTitle', { @@ -56,7 +56,7 @@ export function infra(kibana: any) { defaultMessage: 'Explore your logs', }), icon: 'plugins/infra/images/logging_mono_white.svg', - euiIconType: 'loggingApp', + euiIconType: 'logsApp', id: 'infra:logs', order: 8001, title: i18n.translate('xpack.infra.linkLogsTitle', { @@ -76,7 +76,7 @@ export function infra(kibana: any) { { path: `/app/${APP_ID}#/logs`, label: logsSampleDataLinkLabel, - icon: 'loggingApp', + icon: 'logsApp', }, ]); }, diff --git a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx index 5293af666723a..aa3616c5ecfbe 100644 --- a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx +++ b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx @@ -96,7 +96,7 @@ export const MetricsExplorerChartContextMenu = ({ name: i18n.translate('xpack.infra.metricsExplorer.filterByLabel', { defaultMessage: 'Add filter', }), - icon: 'infraApp', + icon: 'metricsApp', onClick: handleFilter, 'data-test-subj': 'metricsExplorerAction-AddFilter', }, @@ -111,7 +111,7 @@ export const MetricsExplorerChartContextMenu = ({ defaultMessage: 'View metrics for {name}', values: { name: nodeType }, }), - icon: 'infraApp', + icon: 'metricsApp', href: createNodeDetailLink(nodeType, series.id, timeRange.from, timeRange.to), 'data-test-subj': 'metricsExplorerAction-ViewNodeMetrics', }, diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results.tsx index d834b825e414a..8bd9f1074ac54 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results.tsx @@ -14,11 +14,13 @@ export const useLogAnalysisResults = ({ startTime, endTime, bucketDuration = 15 * 60 * 1000, + lastRequestTime, }: { sourceId: string; startTime: number; endTime: number; bucketDuration?: number; + lastRequestTime: number; }) => { const { isLoading: isLoadingLogEntryRate, logEntryRate, getLogEntryRate } = useLogEntryRate({ sourceId, @@ -31,7 +33,7 @@ export const useLogAnalysisResults = ({ useEffect(() => { getLogEntryRate(); - }, [sourceId, startTime, endTime, bucketDuration]); + }, [sourceId, startTime, endTime, bucketDuration, lastRequestTime]); return { isLoading, diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx index e846d4e9e4ac5..ffc48a0af9de9 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx @@ -57,9 +57,13 @@ export const AnalysisResultsContent = ({ setAutoRefresh, } = useLogAnalysisResultsUrlState(); - const [queryTimeRange, setQueryTimeRange] = useState( - stringToNumericTimeRange(selectedTimeRange) - ); + const [queryTimeRange, setQueryTimeRange] = useState<{ + value: TimeRange; + lastChangedTime: number; + }>(() => ({ + value: stringToNumericTimeRange(selectedTimeRange), + lastChangedTime: Date.now(), + })); const bucketDuration = useMemo(() => { // This function takes the current time range in ms, @@ -69,18 +73,21 @@ export const AnalysisResultsContent = ({ // 900000 (15 minutes) to it, so that we don't end up with // jaggy bucket boundaries between the ML buckets and our // aggregation buckets. - const msRange = moment(queryTimeRange.endTime).diff(moment(queryTimeRange.startTime)); + const msRange = moment(queryTimeRange.value.endTime).diff( + moment(queryTimeRange.value.startTime) + ); const bucketIntervalInMs = msRange / 100; const result = bucketSpan * Math.round(bucketIntervalInMs / bucketSpan); const roundedResult = parseInt(Number(result).toFixed(0), 10); return roundedResult < bucketSpan ? bucketSpan : roundedResult; - }, [queryTimeRange.startTime, queryTimeRange.endTime]); + }, [queryTimeRange.value.startTime, queryTimeRange.value.endTime]); const { isLoading, logEntryRate } = useLogAnalysisResults({ sourceId, - startTime: queryTimeRange.startTime, - endTime: queryTimeRange.endTime, + startTime: queryTimeRange.value.startTime, + endTime: queryTimeRange.value.endTime, bucketDuration, + lastRequestTime: queryTimeRange.lastChangedTime, }); const hasResults = useMemo(() => logEntryRate && logEntryRate.histogramBuckets.length > 0, [ logEntryRate, @@ -88,7 +95,10 @@ export const AnalysisResultsContent = ({ const handleQueryTimeRangeChange = useCallback( ({ start: startTime, end: endTime }: { start: string; end: string }) => { - setQueryTimeRange(stringToNumericTimeRange({ startTime, endTime })); + setQueryTimeRange({ + value: stringToNumericTimeRange({ startTime, endTime }), + lastChangedTime: Date.now(), + }); }, [setQueryTimeRange] ); @@ -141,6 +151,16 @@ export const AnalysisResultsContent = ({ fetchJobStatus(); }, JOB_STATUS_POLLING_INTERVAL); + useInterval( + () => { + handleQueryTimeRangeChange({ + start: selectedTimeRange.startTime, + end: selectedTimeRange.endTime, + }); + }, + autoRefresh.isPaused ? null : autoRefresh.interval + ); + return ( <> {isLoading && !logEntryRate ? ( @@ -171,9 +191,11 @@ export const AnalysisResultsContent = ({ ), startTime: ( - {moment(queryTimeRange.startTime).format(dateFormat)} + {moment(queryTimeRange.value.startTime).format(dateFormat)} + ), + endTime: ( + {moment(queryTimeRange.value.endTime).format(dateFormat)} ), - endTime: {moment(queryTimeRange.endTime).format(dateFormat)}, }} /> @@ -187,7 +209,6 @@ export const AnalysisResultsContent = ({ isPaused={autoRefresh.isPaused} refreshInterval={autoRefresh.interval} onRefreshChange={handleAutoRefreshChange} - onRefresh={handleQueryTimeRangeChange} />
@@ -200,7 +221,7 @@ export const AnalysisResultsContent = ({ isLoading={isLoading} results={logEntryRate} setTimeRange={handleChartTimeRangeChange} - timeRange={queryTimeRange} + timeRange={queryTimeRange.value} /> @@ -214,7 +235,7 @@ export const AnalysisResultsContent = ({ results={logEntryRate} setTimeRange={handleChartTimeRangeChange} setupStatus={setupStatus} - timeRange={queryTimeRange} + timeRange={queryTimeRange.value} jobId={jobIds['log-entry-rate']} /> diff --git a/x-pack/legacy/plugins/infra/public/register_feature.ts b/x-pack/legacy/plugins/infra/public/register_feature.ts index 2557503ef5c0a..bf56db77e360f 100644 --- a/x-pack/legacy/plugins/infra/public/register_feature.ts +++ b/x-pack/legacy/plugins/infra/public/register_feature.ts @@ -21,7 +21,7 @@ FeatureCatalogueRegistryProvider.register((i18n: I18nServiceType) => ({ defaultMessage: 'Explore infrastructure metrics and logs for common servers, containers, and services.', }), - icon: 'infraApp', + icon: 'metricsApp', path: `/app/${APP_ID}#infrastructure`, showOnHomePage: true, category: FeatureCatalogueCategory.DATA, @@ -36,7 +36,7 @@ FeatureCatalogueRegistryProvider.register((i18n: I18nServiceType) => ({ defaultMessage: 'Stream logs in real time or scroll through historical views in a console-like experience.', }), - icon: 'loggingApp', + icon: 'logsApp', path: `/app/${APP_ID}#logs`, showOnHomePage: true, category: FeatureCatalogueCategory.DATA, diff --git a/x-pack/legacy/plugins/infra/server/kibana.index.ts b/x-pack/legacy/plugins/infra/server/kibana.index.ts index 59e3ffb3cb33d..48ef846ec5275 100644 --- a/x-pack/legacy/plugins/infra/server/kibana.index.ts +++ b/x-pack/legacy/plugins/infra/server/kibana.index.ts @@ -35,7 +35,7 @@ export const initServerWithKibana = (kbnServer: KbnServer) => { name: i18n.translate('xpack.infra.featureRegistry.linkInfrastructureTitle', { defaultMessage: 'Metrics', }), - icon: 'infraApp', + icon: 'metricsApp', navLinkId: 'infra:home', app: ['infra', 'kibana'], catalogue: ['infraops'], @@ -73,7 +73,7 @@ export const initServerWithKibana = (kbnServer: KbnServer) => { name: i18n.translate('xpack.infra.featureRegistry.linkLogsTitle', { defaultMessage: 'Logs', }), - icon: 'loggingApp', + icon: 'logsApp', navLinkId: 'infra:logs', app: ['infra', 'kibana'], catalogue: ['infralogging'], diff --git a/x-pack/legacy/plugins/lens/index.ts b/x-pack/legacy/plugins/lens/index.ts index a8984bd80fb1c..20f92ebbe0654 100644 --- a/x-pack/legacy/plugins/lens/index.ts +++ b/x-pack/legacy/plugins/lens/index.ts @@ -18,6 +18,7 @@ export const lens: LegacyPluginInitializer = kibana => { return new kibana.Plugin({ id: PLUGIN_ID, configPrefix: `xpack.${PLUGIN_ID}`, + // task_manager could be required, but is only used for telemetry require: ['kibana', 'elasticsearch', 'xpack_main', 'interpreter', 'data'], publicDir: resolve(__dirname, 'public'), diff --git a/x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx b/x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx index 77d0d5a5305aa..87f0872c52343 100644 --- a/x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx +++ b/x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx @@ -17,17 +17,22 @@ import { mount } from 'enzyme'; import { dataPluginMock } from '../../../../../../src/plugins/data/public/mocks'; const dataStartMock = dataPluginMock.createStartContract(); -import { - TopNavMenu, - TopNavMenuData, -} from '../../../../../../src/legacy/core_plugins/kibana_react/public'; +import { TopNavMenuData } from '../../../../../../src/legacy/core_plugins/navigation/public'; import { DataStart } from '../../../../../../src/legacy/core_plugins/data/public'; import { coreMock } from 'src/core/public/mocks'; -jest.mock('../../../../../../src/legacy/core_plugins/kibana_react/public', () => ({ - TopNavMenu: jest.fn(() => null), +jest.mock('../../../../../../src/legacy/core_plugins/navigation/public/legacy', () => ({ + start: { + ui: { + TopNavMenu: jest.fn(() => null), + }, + }, })); +import { start as navigation } from '../../../../../../src/legacy/core_plugins/navigation/public/legacy'; + +const { TopNavMenu } = navigation.ui; + jest.mock('ui/new_platform'); jest.mock('../persistence'); jest.mock('src/core/public'); @@ -127,7 +132,7 @@ describe('Lens App', () => { beforeEach(() => { frame = createMockFrame(); - core = coreMock.createStart(); + core = coreMock.createStart({ basePath: '/testbasepath' }); core.uiSettings.get.mockImplementation( jest.fn(type => { @@ -140,9 +145,6 @@ describe('Lens App', () => { } }) ); - - (core.http.basePath.get as jest.Mock).mockReturnValue(`/testbasepath`); - (core.http.basePath.prepend as jest.Mock).mockImplementation(s => `/testbasepath${s}`); }); it('renders the editor frame', () => { @@ -494,6 +496,8 @@ describe('Lens App', () => { expect(TopNavMenu).toHaveBeenCalledWith( expect.objectContaining({ query: { query: '', language: 'kuery' }, + dateRangeFrom: 'now-7d', + dateRangeTo: 'now', }), {} ); @@ -568,6 +572,8 @@ describe('Lens App', () => { expect(TopNavMenu).toHaveBeenCalledWith( expect.objectContaining({ query: { query: 'new', language: 'lucene' }, + dateRangeFrom: 'now-14d', + dateRangeTo: 'now-7d', }), {} ); diff --git a/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx b/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx index 1152a3de77181..bd75198714dc3 100644 --- a/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx +++ b/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx @@ -20,7 +20,7 @@ import { Query, } from 'src/legacy/core_plugins/data/public'; import { Filter } from '@kbn/es-query'; -import { TopNavMenu } from '../../../../../../src/legacy/core_plugins/kibana_react/public'; +import { start as navigation } from '../../../../../../src/legacy/core_plugins/navigation/public/legacy'; import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; import { Document, SavedObjectStore } from '../persistence'; import { EditorFrameInstance } from '../types'; @@ -163,6 +163,8 @@ export function App({ [] ); + const { TopNavMenu } = navigation.ui; + return (
diff --git a/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx b/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx index 1f8779bb68b81..b7960b23651c6 100644 --- a/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx +++ b/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx @@ -72,7 +72,6 @@ export class AppPlugin { setReportManager( new LensReportManager({ storage: new Storage(localStorage), - basePath: core.http.basePath.get(), http: core.http, }) ); diff --git a/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/expression.tsx b/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/expression.tsx index 23b49ecc168cc..e350e36b3bdc0 100644 --- a/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/expression.tsx +++ b/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/expression.tsx @@ -11,7 +11,10 @@ import { EuiBasicTable } from '@elastic/eui'; import { ExpressionFunction } from '../../../../../../src/plugins/expressions/common'; import { KibanaDatatable } from '../../../../../../src/legacy/core_plugins/interpreter/public'; import { LensMultiTable } from '../types'; -import { IInterpreterRenderFunction } from '../../../../../../src/legacy/core_plugins/expressions/public'; +import { + IInterpreterRenderFunction, + IInterpreterRenderHandlers, +} from '../../../../../../src/legacy/core_plugins/expressions/public'; import { FormatFactory } from '../../../../../../src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities'; import { VisualizationContainer } from '../visualization_container'; @@ -113,8 +116,19 @@ export const getDatatableRenderer = ( help: '', validate: () => {}, reuseDomNode: true, - render: async (domNode: Element, config: DatatableProps, _handlers: unknown) => { - ReactDOM.render(, domNode); + render: async ( + domNode: Element, + config: DatatableProps, + handlers: IInterpreterRenderHandlers + ) => { + ReactDOM.render( + , + domNode, + () => { + handlers.done(); + } + ); + handlers.onDestroy(() => ReactDOM.unmountComponentAtNode(domNode)); }, }); diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/_expression_renderer.scss b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/_expression_renderer.scss index 5d181b9c06da8..8f8dc2323b0b7 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/_expression_renderer.scss +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/_expression_renderer.scss @@ -1,11 +1,12 @@ .lnsExpressionRenderer { + position: relative; width: 100%; height: 100%; display: flex; overflow-x: hidden; padding: $euiSize; -} -.lnsExpressionRenderer > * { - flex: 1; + > .expExpressionRenderer { + position: static; // Let the progress indicator position itself against the outer parent + } } diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/_suggestion_panel.scss b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/_suggestion_panel.scss index 0e89bbe2da968..cb4ca425afc84 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/_suggestion_panel.scss +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/_suggestion_panel.scss @@ -16,6 +16,7 @@ } .lnsSuggestionPanel__button { + position: relative; // Let the expression progress indicator position itself against the button flex: 0 0 auto; width: $lnsSuggestionWidth !important; // sass-lint:disable-line no-important height: $lnsSuggestionHeight; @@ -45,16 +46,22 @@ .lnsSuggestionPanel__chartWrapper { display: flex; - height: $lnsSuggestionHeight - $euiSize; + height: 100%; + width: 100%; pointer-events: none; - margin: 0 $euiSizeS; + padding: $euiSizeS; + + > .expExpressionRenderer { + position: static; // Let the progress indicator position itself against the button + } } .lnsSuggestionPanel__chartWrapper--withLabel { - height: $lnsSuggestionHeight - $euiSizeL; + height: calc(100% - #{$euiSizeL}); } .lnsSuggestionPanel__buttonLabel { + @include euiTextTruncate; @include euiFontSizeXS; display: block; font-weight: $euiFontWeightBold; diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/_workspace_panel_wrapper.scss b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/_workspace_panel_wrapper.scss index 7811df93ba8ce..3ef387eca1e8b 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/_workspace_panel_wrapper.scss +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/_workspace_panel_wrapper.scss @@ -22,6 +22,7 @@ align-items: stretch; justify-content: stretch; overflow: auto; + position: relative; > * { flex: 1 1 100%; diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/chart_switch.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/chart_switch.tsx index 26db1224eb352..2e95aa10bf4db 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/chart_switch.tsx +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/chart_switch.tsx @@ -52,8 +52,8 @@ function VisualizationSummary(props: Props) { if (!visualization) { return ( <> - {i18n.translate('xpack.lens.configPanel.chooseVisualization', { - defaultMessage: 'Choose a visualization', + {i18n.translate('xpack.lens.configPanel.selectVisualization', { + defaultMessage: 'Select a visualization', })} ); @@ -201,8 +201,8 @@ export function ChartSwitch(props: Props) { anchorPosition="downLeft" > - {i18n.translate('xpack.lens.configPanel.chooseVisualization', { - defaultMessage: 'Choose a visualization', + {i18n.translate('xpack.lens.configPanel.selectVisualization', { + defaultMessage: 'Select a visualization', })} diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.tsx index 7d1f400f484e1..6aee215d11591 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.tsx +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/suggestion_panel.tsx @@ -63,38 +63,33 @@ const PreviewRenderer = ({ expression: string; ExpressionRendererComponent: ExpressionRenderer; }) => { - const [expressionError, setExpressionError] = useState(false); - - useEffect(() => { - setExpressionError(false); - }, [expression]); - - return expressionError ? ( -
- -
- ) : ( - { - // eslint-disable-next-line no-console - console.error(`Failed to render preview: `, e); - setExpressionError(true); - }} - /> + > + { + return ( +
+ +
+ ); + }} + /> +
); }; @@ -259,20 +254,27 @@ export function SuggestionPanel({ {stagedPreview && ( - { - trackUiEvent('suggestion_confirmed'); - dispatch({ - type: 'SUBMIT_SUGGESTION', - }); - }} - > - {i18n.translate('xpack.lens.sugegstion.confirmSuggestionLabel', { - defaultMessage: 'Reload suggestions', + + > + { + trackUiEvent('suggestion_confirmed'); + dispatch({ + type: 'SUBMIT_SUGGESTION', + }); + }} + > + {i18n.translate('xpack.lens.sugegstion.refreshSuggestionLabel', { + defaultMessage: 'Refresh', + })} + + )} diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.test.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.test.tsx index ddb82565e4b8b..9cf59f69c0cc2 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.test.tsx +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.test.tsx @@ -392,190 +392,135 @@ describe('workspace_panel', () => { expect(expressionRendererMock).toHaveBeenCalledTimes(2); }); - describe('expression failures', () => { - it('should show an error message if the expression fails to parse', () => { - mockDatasource.toExpression.mockReturnValue('|||'); - mockDatasource.getLayers.mockReturnValue(['first']); - const framePublicAPI = createMockFramePublicAPI(); - framePublicAPI.datasourceLayers = { - first: mockDatasource.publicAPIMock, - }; - - instance = mount( - 'vis' }, - }} - visualizationState={{}} - dispatch={() => {}} - ExpressionRenderer={expressionRendererMock} - core={coreMock.createSetup()} - /> - ); - - expect(instance.find('[data-test-subj="expression-failure"]').first()).toBeTruthy(); - expect(instance.find(expressionRendererMock)).toHaveLength(0); - }); - - it('should show an error message if the expression fails to render', async () => { - mockDatasource.toExpression.mockReturnValue('datasource'); - mockDatasource.getLayers.mockReturnValue(['first']); - const framePublicAPI = createMockFramePublicAPI(); - framePublicAPI.datasourceLayers = { - first: mockDatasource.publicAPIMock, - }; - expressionRendererMock = jest.fn(({ onRenderFailure }) => { - Promise.resolve().then(() => onRenderFailure!({ type: 'error' })); - return ; - }); - - instance = mount( - 'vis' }, - }} - visualizationState={{}} - dispatch={() => {}} - ExpressionRenderer={expressionRendererMock} - core={coreMock.createSetup()} - /> - ); - - // "wait" for the expression to execute - await waitForPromises(); + it('should show an error message if the expression fails to parse', () => { + mockDatasource.toExpression.mockReturnValue('|||'); + mockDatasource.getLayers.mockReturnValue(['first']); + const framePublicAPI = createMockFramePublicAPI(); + framePublicAPI.datasourceLayers = { + first: mockDatasource.publicAPIMock, + }; - instance.update(); + instance = mount( + 'vis' }, + }} + visualizationState={{}} + dispatch={() => {}} + ExpressionRenderer={expressionRendererMock} + core={coreMock.createSetup()} + /> + ); - expect(instance.find('EuiFlexItem[data-test-subj="expression-failure"]')).toHaveLength(1); - expect(instance.find(expressionRendererMock)).toHaveLength(0); - }); + expect(instance.find('[data-test-subj="expression-failure"]').first()).toBeTruthy(); + expect(instance.find(expressionRendererMock)).toHaveLength(0); + }); - it('should not attempt to run the expression again if it does not change', async () => { - mockDatasource.toExpression.mockReturnValue('datasource'); - mockDatasource.getLayers.mockReturnValue(['first']); - const framePublicAPI = createMockFramePublicAPI(); - framePublicAPI.datasourceLayers = { - first: mockDatasource.publicAPIMock, - }; - expressionRendererMock = jest.fn(({ onRenderFailure }) => { - Promise.resolve().then(() => onRenderFailure!({ type: 'error' })); - return ; - }); + it('should not attempt to run the expression again if it does not change', async () => { + mockDatasource.toExpression.mockReturnValue('datasource'); + mockDatasource.getLayers.mockReturnValue(['first']); + const framePublicAPI = createMockFramePublicAPI(); + framePublicAPI.datasourceLayers = { + first: mockDatasource.publicAPIMock, + }; - instance = mount( - 'vis' }, - }} - visualizationState={{}} - dispatch={() => {}} - ExpressionRenderer={expressionRendererMock} - core={coreMock.createSetup()} - /> - ); + instance = mount( + 'vis' }, + }} + visualizationState={{}} + dispatch={() => {}} + ExpressionRenderer={expressionRendererMock} + core={coreMock.createSetup()} + /> + ); - // "wait" for the expression to execute - await waitForPromises(); + // "wait" for the expression to execute + await waitForPromises(); - instance.update(); + instance.update(); - expect(expressionRendererMock).toHaveBeenCalledTimes(1); + expect(expressionRendererMock).toHaveBeenCalledTimes(1); - instance.update(); + instance.update(); - expect(expressionRendererMock).toHaveBeenCalledTimes(1); - }); + expect(expressionRendererMock).toHaveBeenCalledTimes(1); + }); - it('should attempt to run the expression again if changes after an error', async () => { - mockDatasource.toExpression.mockReturnValue('datasource'); - mockDatasource.getLayers.mockReturnValue(['first']); - const framePublicAPI = createMockFramePublicAPI(); - framePublicAPI.datasourceLayers = { - first: mockDatasource.publicAPIMock, - }; - expressionRendererMock = jest.fn(({ onRenderFailure }) => { - Promise.resolve().then(() => onRenderFailure!({ type: 'error' })); - return ; - }); + it('should attempt to run the expression again if it changes', async () => { + mockDatasource.toExpression.mockReturnValue('datasource'); + mockDatasource.getLayers.mockReturnValue(['first']); + const framePublicAPI = createMockFramePublicAPI(); + framePublicAPI.datasourceLayers = { + first: mockDatasource.publicAPIMock, + }; - instance = mount( - 'vis' }, - }} - visualizationState={{}} - dispatch={() => {}} - ExpressionRenderer={expressionRendererMock} - core={coreMock.createSetup()} - /> - ); + instance = mount( + 'vis' }, + }} + visualizationState={{}} + dispatch={() => {}} + ExpressionRenderer={expressionRendererMock} + core={coreMock.createSetup()} + /> + ); - // "wait" for the expression to execute - await waitForPromises(); + // "wait" for the expression to execute + await waitForPromises(); - instance.update(); + instance.update(); - expect(expressionRendererMock).toHaveBeenCalledTimes(1); + expect(expressionRendererMock).toHaveBeenCalledTimes(1); - expressionRendererMock.mockImplementation(_ => { - return ; - }); + expressionRendererMock.mockImplementation(_ => { + return ; + }); - instance.setProps({ visualizationState: {} }); - instance.update(); + instance.setProps({ visualizationState: {} }); + instance.update(); - expect(expressionRendererMock).toHaveBeenCalledTimes(2); + expect(expressionRendererMock).toHaveBeenCalledTimes(2); - expect(instance.find(expressionRendererMock)).toHaveLength(1); - }); + expect(instance.find(expressionRendererMock)).toHaveLength(1); }); describe('suggestions from dropping in workspace panel', () => { diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.tsx index 1fb1af46f6f0d..d72bf541fd43e 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.tsx +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.tsx @@ -8,15 +8,14 @@ import React, { useState, useEffect, useMemo, useContext } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { - EuiCodeBlock, EuiFlexGroup, EuiFlexItem, + EuiIcon, EuiImage, EuiText, EuiBetaBadge, EuiButtonEmpty, } from '@elastic/eui'; -import { toExpression } from '@kbn/interpreter/common'; import { CoreStart, CoreSetup } from 'src/core/public'; import { ExpressionRenderer } from '../../../../../../../src/legacy/core_plugins/expressions/public'; import { Action } from './state_management'; @@ -92,7 +91,10 @@ export function InnerWorkspacePanel({ return suggestions.find(s => s.visualizationId === activeVisualizationId) || suggestions[0]; }, [dragDropContext.dragging]); - const [expressionError, setExpressionError] = useState(undefined); + const [localState, setLocalState] = useState({ + expressionBuildError: undefined as string | undefined, + expandError: false, + }); const activeVisualization = activeVisualizationId ? visualizationMap[activeVisualizationId] @@ -107,7 +109,8 @@ export function InnerWorkspacePanel({ framePublicAPI, }); } catch (e) { - setExpressionError(e.toString()); + // Most likely an error in the expression provided by a datasource or visualization + setLocalState(s => ({ ...s, expressionBuildError: e.toString() })); } }, [ activeVisualization, @@ -178,8 +181,11 @@ export function InnerWorkspacePanel({ function renderVisualization() { useEffect(() => { // reset expression error if component attempts to run it again - if (expressionError) { - setExpressionError(undefined); + if (expression && localState.expressionBuildError) { + setLocalState(s => ({ + ...s, + expressionBuildError: undefined, + })); } }, [expression]); @@ -187,37 +193,63 @@ export function InnerWorkspacePanel({ return renderEmptyWorkspace(); } - if (expressionError) { + if (localState.expressionBuildError) { return ( - + + + + - {/* TODO word this differently because expressions should not be exposed at this level */} - {expression && ( - - {toExpression(expression)} - - )} - - {JSON.stringify(expressionError, null, 2)} - + {localState.expressionBuildError} ); - } else { - return ( + } + + return ( +
{ - setExpressionError(e); + renderError={(errorMessage?: string | null) => { + return ( + + + + + + + + {errorMessage ? ( + + { + setLocalState(prevState => ({ + ...prevState, + expandError: !prevState.expandError, + })); + }} + > + {i18n.translate('xpack.lens.editorFrame.expandRenderingErrorButton', { + defaultMessage: 'Show details of error', + })} + + + {localState.expandError ? errorMessage : null} + + ) : null} + + ); }} /> - ); - } +
+ ); } return ( diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/embeddable/embeddable.test.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/embeddable/embeddable.test.tsx index 96624764bb8ca..d728457b7e3a3 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/embeddable/embeddable.test.tsx +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/embeddable/embeddable.test.tsx @@ -10,7 +10,6 @@ import { Query } from 'src/legacy/core_plugins/data/public'; import { ExpressionRendererProps } from 'src/legacy/core_plugins/expressions/public'; import { Filter } from '@kbn/es-query'; import { Document } from '../../persistence'; -import { act } from 'react-dom/test-utils'; jest.mock('../../../../../../../src/legacy/ui/public/inspector', () => ({ isAvailable: false, @@ -61,25 +60,6 @@ describe('embeddable', () => { expect(expressionRenderer.mock.calls[0][0]!.expression).toEqual(savedVis.expression); }); - it('should display error if expression renderering fails', () => { - const embeddable = new Embeddable( - expressionRenderer, - { - editUrl: '', - editable: true, - savedVis, - }, - { id: '123' } - ); - embeddable.render(mountpoint); - - act(() => { - expressionRenderer.mock.calls[0][0]!.onRenderFailure!({ type: 'error' }); - }); - - expect(mountpoint.innerHTML).toContain("Visualization couldn't be displayed"); - }); - it('should re-render if new input is pushed', () => { const timeRange: TimeRange = { from: 'now-15d', to: 'now' }; const query: Query = { language: 'kquery', query: '' }; diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/embeddable/expression_wrapper.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/embeddable/expression_wrapper.tsx index daf5e0c8d0dca..bd7588fcfedae 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/embeddable/expression_wrapper.tsx +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/embeddable/expression_wrapper.tsx @@ -5,7 +5,7 @@ */ import _ from 'lodash'; -import React, { useState, useEffect } from 'react'; +import React from 'react'; import { I18nProvider } from '@kbn/i18n/react'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -31,16 +31,9 @@ export function ExpressionWrapper({ expression, context, }: ExpressionWrapperProps) { - const [expressionError, setExpressionError] = useState(undefined); - useEffect(() => { - // reset expression error if component attempts to run it again - if (expressionError) { - setExpressionError(undefined); - } - }, [expression, context]); return ( - {expression === '' || expressionError ? ( + {expression === '' ? ( @@ -55,14 +48,12 @@ export function ExpressionWrapper({ ) : ( - { - setExpressionError(e); - }} - searchContext={{ ...context, type: 'kibana_context' }} - /> +
+ +
)}
); diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/change_indexpattern.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/change_indexpattern.tsx index dc42b97cce08d..4d9bc6276d52a 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/change_indexpattern.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/change_indexpattern.tsx @@ -5,8 +5,15 @@ */ import _ from 'lodash'; +import { i18n } from '@kbn/i18n'; import React, { useState } from 'react'; -import { EuiButtonEmpty, EuiPopover, EuiSelectable, EuiButtonEmptyProps } from '@elastic/eui'; +import { + EuiButtonEmpty, + EuiPopover, + EuiPopoverTitle, + EuiSelectable, + EuiButtonEmptyProps, +} from '@elastic/eui'; import { EuiSelectableProps } from '@elastic/eui/src/components/selectable/selectable'; import { IndexPatternRef } from './types'; import { trackUiEvent } from '../lens_ui_telemetry'; @@ -62,6 +69,11 @@ export function ChangeIndexPattern({ ownFocus >
+ + {i18n.translate('xpack.lens.indexPattern.changeIndexPatternTitle', { + defaultMessage: 'Change index pattern', + })} + } @@ -441,31 +441,35 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ {paginatedFields.length === 0 && (

- - {showEmptyFields - ? i18n.translate('xpack.lens.indexPatterns.hiddenFieldsLabel', { + {showEmptyFields + ? localState.typeFilter.length || localState.nameFilter.length + ? i18n.translate('xpack.lens.indexPatterns.noFilteredFieldsLabel', { defaultMessage: - 'No fields have data with the current filters. You can show fields without data using the filters above.', + 'No fields match the current filters. Try changing your filters or time range.', }) : i18n.translate('xpack.lens.indexPatterns.noFieldsLabel', { - defaultMessage: 'No fields in {title} can be visualized.', - values: { title: currentIndexPattern.title }, - })} - + defaultMessage: 'No fields exist in this index pattern.', + }) + : i18n.translate('xpack.lens.indexPatterns.emptyFieldsWithDataLabel', { + defaultMessage: + 'No fields have data with the current filters and time range. Try changing your filters or time range.', + })}

+ {(!showEmptyFields || localState.typeFilter.length || localState.nameFilter.length) && ( { - trackUiEvent('show_empty_fields_clicked'); + trackUiEvent('indexpattern_show_all_fields_clicked'); clearLocalState(); onToggleEmptyFields(true); }} > {i18n.translate('xpack.lens.indexPatterns.showAllFields.buttonText', { - defaultMessage: 'Show All Fields', + defaultMessage: 'Show all fields', })} )} diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.tsx index 41a4bd3549dc1..79a3ac4137f88 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.tsx @@ -174,10 +174,8 @@ export function FieldItem(props: FieldItemProps) { togglePopover(); } }} - aria-label={i18n.translate('xpack.lens.indexPattern.fieldStatsButtonAriaLabel', { - defaultMessage: - 'Click or press Enter for information about {fieldName}. Or, drag field into visualization.', - values: { fieldName: field.name }, + aria-label={i18n.translate('xpack.lens.indexPattern.fieldStatsButtonLabel', { + defaultMessage: 'Click for a field preview. Or, drag and drop to visualize.', })} > @@ -188,10 +186,8 @@ export function FieldItem(props: FieldItemProps) { {i18n.translate('xpack.lens.indexPattern.fieldStatsNoData', { - defaultMessage: 'No data to display', + defaultMessage: 'No data to display.', })}
); diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.test.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.test.ts index 845cfa29d5724..b4f01078f1f78 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.test.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.test.ts @@ -240,7 +240,7 @@ describe('IndexPattern Data Source', () => { columnOrder: ['col1', 'col2'], columns: { col1: { - label: 'Count of Documents', + label: 'Count of records', dataType: 'number', isBucketed: false, @@ -272,7 +272,7 @@ describe('IndexPattern Data Source', () => { metricsAtAllLevels=false partialRows=false includeFormatHints=true - aggConfigs={lens_auto_date aggConfigs='[{\\"id\\":\\"col1\\",\\"enabled\\":true,\\"type\\":\\"count\\",\\"schema\\":\\"metric\\",\\"params\\":{}},{\\"id\\":\\"col2\\",\\"enabled\\":true,\\"type\\":\\"date_histogram\\",\\"schema\\":\\"segment\\",\\"params\\":{\\"field\\":\\"timestamp\\",\\"useNormalizedEsInterval\\":true,\\"interval\\":\\"1d\\",\\"drop_partials\\":false,\\"min_doc_count\\":0,\\"extended_bounds\\":{}}}]'} | lens_rename_columns idMap='{\\"col-0-col1\\":{\\"label\\":\\"Count of Documents\\",\\"dataType\\":\\"number\\",\\"isBucketed\\":false,\\"operationType\\":\\"count\\",\\"id\\":\\"col1\\"},\\"col-1-col2\\":{\\"label\\":\\"Date\\",\\"dataType\\":\\"date\\",\\"isBucketed\\":true,\\"operationType\\":\\"date_histogram\\",\\"sourceField\\":\\"timestamp\\",\\"params\\":{\\"interval\\":\\"1d\\"},\\"id\\":\\"col2\\"}}'" + aggConfigs={lens_auto_date aggConfigs='[{\\"id\\":\\"col1\\",\\"enabled\\":true,\\"type\\":\\"count\\",\\"schema\\":\\"metric\\",\\"params\\":{}},{\\"id\\":\\"col2\\",\\"enabled\\":true,\\"type\\":\\"date_histogram\\",\\"schema\\":\\"segment\\",\\"params\\":{\\"field\\":\\"timestamp\\",\\"useNormalizedEsInterval\\":true,\\"interval\\":\\"1d\\",\\"drop_partials\\":false,\\"min_doc_count\\":0,\\"extended_bounds\\":{}}}]'} | lens_rename_columns idMap='{\\"col-0-col1\\":{\\"label\\":\\"Count of records\\",\\"dataType\\":\\"number\\",\\"isBucketed\\":false,\\"operationType\\":\\"count\\",\\"id\\":\\"col1\\"},\\"col-1-col2\\":{\\"label\\":\\"Date\\",\\"dataType\\":\\"date\\",\\"isBucketed\\":true,\\"operationType\\":\\"date_histogram\\",\\"sourceField\\":\\"timestamp\\",\\"params\\":{\\"interval\\":\\"1d\\"},\\"id\\":\\"col2\\"}}'" `); }); }); diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/count.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/count.tsx index 68a36787ec189..94cb17b340c36 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/count.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/count.tsx @@ -9,7 +9,7 @@ import { OperationDefinition } from '.'; import { ParameterlessIndexPatternColumn, BaseIndexPatternColumn } from './column_types'; const countLabel = i18n.translate('xpack.lens.indexPattern.countOf', { - defaultMessage: 'Count of documents', + defaultMessage: 'Count of records', }); export type CountIndexPatternColumn = ParameterlessIndexPatternColumn< diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.tsx index 89c4899bb8e56..e5c00542df7d3 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.tsx @@ -16,7 +16,7 @@ import { IndexPattern } from '../../types'; type PropType = C extends React.ComponentType ? P : unknown; const autoInterval = 'auto'; -const supportedIntervals = ['M', 'w', 'd', 'h']; +const supportedIntervals = ['M', 'w', 'd', 'h', 'm']; const defaultCustomInterval = supportedIntervals[2]; // Add ticks to EuiRange component props diff --git a/x-pack/legacy/plugins/lens/public/lens_ui_telemetry/factory.test.ts b/x-pack/legacy/plugins/lens/public/lens_ui_telemetry/factory.test.ts index 9b65cbefaf799..6e860c594f4a5 100644 --- a/x-pack/legacy/plugins/lens/public/lens_ui_telemetry/factory.test.ts +++ b/x-pack/legacy/plugins/lens/public/lens_ui_telemetry/factory.test.ts @@ -45,7 +45,6 @@ describe('Lens UI telemetry', () => { const fakeManager = new LensReportManager({ http, storage, - basePath: '/basepath', }); setReportManager(fakeManager); }); @@ -84,7 +83,7 @@ describe('Lens UI telemetry', () => { jest.runOnlyPendingTimers(); - expect(http.post).toHaveBeenCalledWith(`/basepath/api/lens/telemetry`, { + expect(http.post).toHaveBeenCalledWith(`/api/lens/telemetry`, { body: JSON.stringify({ events: { '2019-10-23': { diff --git a/x-pack/legacy/plugins/lens/public/lens_ui_telemetry/factory.ts b/x-pack/legacy/plugins/lens/public/lens_ui_telemetry/factory.ts index d264e9c77c463..673910deae339 100644 --- a/x-pack/legacy/plugins/lens/public/lens_ui_telemetry/factory.ts +++ b/x-pack/legacy/plugins/lens/public/lens_ui_telemetry/factory.ts @@ -45,21 +45,11 @@ export class LensReportManager { private storage: Storage; private http: HttpServiceBase; - private basePath: string; private timer: ReturnType; - constructor({ - storage, - http, - basePath, - }: { - storage: Storage; - http: HttpServiceBase; - basePath: string; - }) { + constructor({ storage, http }: { storage: Storage; http: HttpServiceBase }) { this.storage = storage; this.http = http; - this.basePath = basePath; this.readFromStorage(); @@ -96,7 +86,7 @@ export class LensReportManager { this.readFromStorage(); if (Object.keys(this.events).length || Object.keys(this.suggestionEvents).length) { try { - await this.http.post(`${this.basePath}${BASE_API_URL}/telemetry`, { + await this.http.post(`${BASE_API_URL}/telemetry`, { body: JSON.stringify({ events: this.events, suggestionEvents: this.suggestionEvents, diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.tsx b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.tsx index 68de585771a79..e70a10576106c 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.tsx +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.tsx @@ -8,7 +8,10 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { ExpressionFunction } from 'src/legacy/core_plugins/interpreter/types'; import { FormatFactory } from 'ui/visualize/loader/pipeline_helpers/utilities'; -import { IInterpreterRenderFunction } from '../../../../../../src/legacy/core_plugins/expressions/public/expressions'; +import { + IInterpreterRenderFunction, + IInterpreterRenderHandlers, +} from '../../../../../../src/legacy/core_plugins/expressions/public/expressions'; import { MetricConfig } from './types'; import { LensMultiTable } from '../types'; import { AutoScale } from './auto_scale'; @@ -76,12 +79,15 @@ export const getMetricChartRenderer = ( formatFactory: FormatFactory ): IInterpreterRenderFunction => ({ name: 'lens_metric_chart_renderer', - displayName: 'Metric Chart', - help: 'Metric Chart Renderer', + displayName: 'Metric chart', + help: 'Metric chart renderer', validate: () => {}, reuseDomNode: true, - render: async (domNode: Element, config: MetricChartProps, _handlers: unknown) => { - ReactDOM.render(, domNode); + render: (domNode: Element, config: MetricChartProps, handlers: IInterpreterRenderHandlers) => { + ReactDOM.render(, domNode, () => { + handlers.done(); + }); + handlers.onDestroy(() => ReactDOM.unmountComponentAtNode(domNode)); }, }); diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/types.ts b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/types.ts index de81cc208070a..f81eb9d72d6ae 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/types.ts +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/types.ts @@ -241,7 +241,7 @@ export const visualizationTypes: VisualizationType[] = [ icon: 'visBarHorizontal', largeIcon: chartBarHorizontalSVG, label: i18n.translate('xpack.lens.xyVisualization.barHorizontalLabel', { - defaultMessage: 'Horizontal Bar', + defaultMessage: 'Horizontal bar', }), }, { @@ -249,7 +249,7 @@ export const visualizationTypes: VisualizationType[] = [ icon: 'visBarVerticalStacked', largeIcon: chartBarStackedSVG, label: i18n.translate('xpack.lens.xyVisualization.stackedBarLabel', { - defaultMessage: 'Stacked Bar', + defaultMessage: 'Stacked bar', }), }, { @@ -257,7 +257,7 @@ export const visualizationTypes: VisualizationType[] = [ icon: 'visBarHorizontalStacked', largeIcon: chartBarHorizontalStackedSVG, label: i18n.translate('xpack.lens.xyVisualization.stackedBarHorizontalLabel', { - defaultMessage: 'Stacked Horizontal Bar', + defaultMessage: 'Stacked horizontal bar', }), }, { @@ -281,7 +281,7 @@ export const visualizationTypes: VisualizationType[] = [ icon: 'visAreaStacked', largeIcon: chartAreaStackedSVG, label: i18n.translate('xpack.lens.xyVisualization.stackedAreaLabel', { - defaultMessage: 'Stacked Area', + defaultMessage: 'Stacked area', }), }, ]; diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_config_panel.tsx b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_config_panel.tsx index f285cb90f225a..f59b1520dbbb4 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_config_panel.tsx +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_config_panel.tsx @@ -20,6 +20,7 @@ import { EuiSpacer, EuiButtonEmpty, EuiPopoverFooter, + EuiToolTip, } from '@elastic/eui'; import { State, SeriesType, LayerConfig, visualizationTypes } from './types'; import { VisualizationProps, OperationMetadata } from '../types'; @@ -235,7 +236,7 @@ export function XYConfigPanel(props: VisualizationProps) { ) { ))} - { - trackUiEvent('xy_layer_added'); - const usedSeriesTypes = _.uniq(state.layers.map(layer => layer.seriesType)); - setState({ - ...state, - layers: [ - ...state.layers, - newLayerState( - usedSeriesTypes.length === 1 ? usedSeriesTypes[0] : state.preferredSeriesType, - frame.addNewLayer() - ), - ], - }); - }} - iconType="plusInCircleFilled" - /> + + + { + trackUiEvent('xy_layer_added'); + const usedSeriesTypes = _.uniq(state.layers.map(layer => layer.seriesType)); + setState({ + ...state, + layers: [ + ...state.layers, + newLayerState( + usedSeriesTypes.length === 1 ? usedSeriesTypes[0] : state.preferredSeriesType, + frame.addNewLayer() + ), + ], + }); + }} + iconType="plusInCircleFilled" + /> + + ); } diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx index 4529179debd26..f55cd8f99a213 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx @@ -20,6 +20,7 @@ import { } from '@elastic/charts'; import { I18nProvider } from '@kbn/i18n/react'; import { ExpressionFunction } from 'src/legacy/core_plugins/interpreter/types'; +import { IInterpreterRenderHandlers } from 'src/legacy/core_plugins/expressions/public'; import { EuiIcon, EuiText, IconType, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; @@ -98,18 +99,20 @@ export const getXyChartRenderer = (dependencies: { timeZone: string; }): IInterpreterRenderFunction => ({ name: 'lens_xy_chart_renderer', - displayName: 'XY Chart', + displayName: 'XY chart', help: i18n.translate('xpack.lens.xyChart.renderer.help', { - defaultMessage: 'X/Y Chart Renderer', + defaultMessage: 'X/Y chart renderer', }), validate: () => {}, reuseDomNode: true, - render: async (domNode: Element, config: XYChartProps, _handlers: unknown) => { + render: (domNode: Element, config: XYChartProps, handlers: IInterpreterRenderHandlers) => { + handlers.onDestroy(() => ReactDOM.unmountComponentAtNode(domNode)); ReactDOM.render( , - domNode + domNode, + () => handlers.done() ); }, }); @@ -121,16 +124,18 @@ function getIconForSeriesType(seriesType: SeriesType): IconType { const MemoizedChart = React.memo(XYChart); export function XYChartReportable(props: XYChartRenderProps) { - const [isReady, setIsReady] = useState(false); + const [state, setState] = useState({ + isReady: false, + }); // It takes a cycle for the XY chart to render. This prevents // reporting from printing a blank chart placeholder. useEffect(() => { - setIsReady(true); + setState({ isReady: true }); }, []); return ( - + ); @@ -229,7 +234,11 @@ export function XYChart({ data, args, formatFactory, timeZone }: XYChartRenderPr }, index ) => { - if (!data.tables[layerId] || data.tables[layerId].rows.length === 0) { + if ( + !data.tables[layerId] || + data.tables[layerId].rows.length === 0 || + data.tables[layerId].rows.every(row => typeof row[xAccessor] === 'undefined') + ) { return; } @@ -238,7 +247,7 @@ export function XYChart({ data, args, formatFactory, timeZone }: XYChartRenderPr const rows = data.tables[layerId].rows.map(row => { const newRow: typeof row = {}; - // Remap data to { 'Count of documents': 5 } + // Remap data to { 'Count of records': 5 } Object.keys(row).forEach(key => { if (columnToLabelMap[key]) { newRow[columnToLabelMap[key]] = row[key]; diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_visualization.test.ts b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_visualization.test.ts index 4b03c6c346ce5..db28e76f82946 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_visualization.test.ts +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_visualization.test.ts @@ -47,7 +47,7 @@ describe('xy_visualization', () => { it('should show mixed xy chart when multilple series types', () => { const desc = xyVisualization.getDescription(mixedState('bar', 'line')); - expect(desc.label).toEqual('Mixed XY Chart'); + expect(desc.label).toEqual('Mixed XY chart'); }); it('should show the preferredSeriesType if there are no layers', () => { @@ -56,7 +56,7 @@ describe('xy_visualization', () => { // 'test-file-stub' is a hack, but it at least means we aren't using // a standard icon here. expect(desc.icon).toEqual('test-file-stub'); - expect(desc.label).toEqual('Bar Chart'); + expect(desc.label).toEqual('Bar chart'); }); it('should show mixed horizontal bar chart when multiple horizontal bar types', () => { @@ -64,23 +64,23 @@ describe('xy_visualization', () => { mixedState('bar_horizontal', 'bar_horizontal_stacked') ); - expect(desc.label).toEqual('Mixed Horizontal Bar Chart'); + expect(desc.label).toEqual('Mixed horizontal bar chart'); }); it('should show bar chart when bar only', () => { const desc = xyVisualization.getDescription(mixedState('bar_horizontal', 'bar_horizontal')); - expect(desc.label).toEqual('Horizontal Bar Chart'); + expect(desc.label).toEqual('Horizontal bar chart'); }); it('should show the chart description if not mixed', () => { - expect(xyVisualization.getDescription(mixedState('area')).label).toEqual('Area Chart'); - expect(xyVisualization.getDescription(mixedState('line')).label).toEqual('Line Chart'); + expect(xyVisualization.getDescription(mixedState('area')).label).toEqual('Area chart'); + expect(xyVisualization.getDescription(mixedState('line')).label).toEqual('Line chart'); expect(xyVisualization.getDescription(mixedState('area_stacked')).label).toEqual( - 'Stacked Area Chart' + 'Stacked area chart' ); expect(xyVisualization.getDescription(mixedState('bar_horizontal_stacked')).label).toEqual( - 'Stacked Horizontal Bar Chart' + 'Stacked horizontal bar chart' ); }); }); @@ -119,7 +119,7 @@ describe('xy_visualization', () => { "position": "right", }, "preferredSeriesType": "bar_stacked", - "title": "Empty XY Chart", + "title": "Empty XY chart", } `); }); diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_visualization.tsx b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_visualization.tsx index b56e0d9deb55e..5ba77cb39d5f8 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_visualization.tsx +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_visualization.tsx @@ -54,7 +54,7 @@ function getDescription(state?: State) { ? visualizationType.label : isHorizontalChart(state.layers) ? i18n.translate('xpack.lens.xyVisualization.mixedBarHorizontalLabel', { - defaultMessage: 'Mixed Horizontal Bar', + defaultMessage: 'Mixed horizontal bar', }) : i18n.translate('xpack.lens.xyVisualization.mixedLabel', { defaultMessage: 'Mixed XY', @@ -70,7 +70,7 @@ export const xyVisualization: Visualization = { getDescription(state) { const { icon, label } = getDescription(state); const chartLabel = i18n.translate('xpack.lens.xyVisualization.chartLabel', { - defaultMessage: '{label} Chart', + defaultMessage: '{label} chart', values: { label }, }); @@ -93,7 +93,7 @@ export const xyVisualization: Visualization = { initialize(frame, state) { return ( state || { - title: 'Empty XY Chart', + title: 'Empty XY chart', legend: { isVisible: true, position: Position.Right }, preferredSeriesType: defaultSeriesType, layers: [ diff --git a/x-pack/legacy/plugins/lens/server/routes/field_stats.ts b/x-pack/legacy/plugins/lens/server/routes/field_stats.ts index e7e378e6b20e4..ceefb492cdabe 100644 --- a/x-pack/legacy/plugins/lens/server/routes/field_stats.ts +++ b/x-pack/legacy/plugins/lens/server/routes/field_stats.ts @@ -7,8 +7,8 @@ import Boom from 'boom'; import DateMath from '@elastic/datemath'; import { schema } from '@kbn/config-schema'; -import { AggregationSearchResponseWithTotalHitsAsInt } from 'elasticsearch'; import { CoreSetup } from 'src/core/server'; +import { ESSearchResponse } from '../../../apm/typings/elasticsearch'; import { FieldStatsResponse, BASE_API_URL } from '../../common'; const SHARD_SIZE = 5000; @@ -135,9 +135,11 @@ export async function getNumberHistogram( }, }; - const minMaxResult = (await aggSearchWithBody( - searchBody - )) as AggregationSearchResponseWithTotalHitsAsInt; + const minMaxResult = (await aggSearchWithBody(searchBody)) as ESSearchResponse< + unknown, + { body: { aggs: typeof searchBody } }, + { restTotalHitsAsInt: true } + >; const minValue = minMaxResult.aggregations!.sample.min_value.value; const maxValue = minMaxResult.aggregations!.sample.max_value.value; @@ -178,11 +180,10 @@ export async function getNumberHistogram( }, }, }; - const histogramResult = (await aggSearchWithBody( - histogramBody - )) as AggregationSearchResponseWithTotalHitsAsInt< + const histogramResult = (await aggSearchWithBody(histogramBody)) as ESSearchResponse< unknown, - { body: { aggs: typeof histogramBody } } + { body: { aggs: typeof histogramBody } }, + { restTotalHitsAsInt: true } >; return { @@ -214,11 +215,10 @@ export async function getStringSamples( }, }, }; - const topValuesResult = (await aggSearchWithBody( - topValuesBody - )) as AggregationSearchResponseWithTotalHitsAsInt< + const topValuesResult = (await aggSearchWithBody(topValuesBody)) as ESSearchResponse< unknown, - { body: { aggs: typeof topValuesBody } } + { body: { aggs: typeof topValuesBody } }, + { restTotalHitsAsInt: true } >; return { @@ -263,11 +263,10 @@ export async function getDateHistogram( const histogramBody = { histo: { date_histogram: { field: field.name, fixed_interval: fixedInterval } }, }; - const results = (await aggSearchWithBody( - histogramBody - )) as AggregationSearchResponseWithTotalHitsAsInt< + const results = (await aggSearchWithBody(histogramBody)) as ESSearchResponse< unknown, - { body: { aggs: typeof histogramBody } } + { body: { aggs: typeof histogramBody } }, + { restTotalHitsAsInt: true } >; return { diff --git a/x-pack/legacy/plugins/lens/server/usage/collectors.ts b/x-pack/legacy/plugins/lens/server/usage/collectors.ts index 9a58026002ade..94a7c8e0d85c1 100644 --- a/x-pack/legacy/plugins/lens/server/usage/collectors.ts +++ b/x-pack/legacy/plugins/lens/server/usage/collectors.ts @@ -89,7 +89,11 @@ async function isTaskManagerReady(server: Server) { } async function getLatestTaskState(server: Server) { - const taskManager = server.plugins.task_manager!; + const taskManager = server.plugins.task_manager; + + if (!taskManager) { + return null; + } try { const result = await taskManager.fetch({ diff --git a/x-pack/legacy/plugins/lens/server/usage/task.ts b/x-pack/legacy/plugins/lens/server/usage/task.ts index ba74bcd240886..3cb857a453e1d 100644 --- a/x-pack/legacy/plugins/lens/server/usage/task.ts +++ b/x-pack/legacy/plugins/lens/server/usage/task.ts @@ -8,17 +8,13 @@ import moment from 'moment'; import KbnServer, { Server } from 'src/legacy/server/kbn_server'; import { CoreSetup } from 'src/core/server'; import { CallClusterOptions } from 'src/legacy/core_plugins/elasticsearch'; -// This import has the side effect of allowing us to use the elasticsearch type -// extensions below. Without this import, the compiler is unable to find these -// in tests -import {} from '../../../apm/typings/elasticsearch'; import { SearchParams, DeleteDocumentByQueryParams, SearchResponse, DeleteDocumentByQueryResponse, - AggregationSearchResponseWithTotalHitsAsInt, } from 'elasticsearch'; +import { ESSearchResponse } from '../../../apm/typings/elasticsearch'; import { XPackMainPlugin } from '../../../xpack_main/xpack_main'; import { RunContext } from '../../../task_manager'; import { getVisualizationCounts } from './visualization_counts'; @@ -49,7 +45,13 @@ export function initializeLensTelemetry(core: CoreSetup, { server }: { server: S } function registerLensTelemetryTask(core: CoreSetup, { server }: { server: Server }) { - const taskManager = server.plugins.task_manager!; + const taskManager = server.plugins.task_manager; + + if (!taskManager) { + server.log(['debug', 'telemetry'], `Task manager is not available`); + return; + } + taskManager.registerTaskDefinitions({ [TELEMETRY_TASK_TYPE]: { title: 'Lens telemetry fetch task', @@ -66,6 +68,11 @@ function scheduleTasks(server: Server) { status: { plugin: { kbnServer: KbnServer } }; }).status.plugin; + if (!taskManager) { + server.log(['debug', 'telemetry'], `Task manager is not available`); + return; + } + kbnServer.afterPluginsInit(() => { // The code block below can't await directly within "afterPluginsInit" // callback due to circular dependency The server isn't "ready" until @@ -75,14 +82,14 @@ function scheduleTasks(server: Server) { // function block. (async () => { try { - await taskManager!.schedule({ + await taskManager.schedule({ id: TASK_ID, taskType: TELEMETRY_TASK_TYPE, state: { byDate: {}, suggestionsByDate: {}, saved: {}, runs: 0 }, params: {}, }); } catch (e) { - server.log(['warning', 'telemetry'], `Error scheduling task, received ${e.message}`); + server.log(['debug', 'telemetry'], `Error scheduling task, received ${e.message}`); } })(); }); @@ -135,11 +142,12 @@ export async function getDailyEvents( }, }; - const metrics: AggregationSearchResponseWithTotalHitsAsInt< + const metrics: ESSearchResponse< unknown, { body: { aggs: typeof aggs }; - } + }, + { restTotalHitsAsInt: true } > = await callCluster('search', { index: kibanaIndex, rest_total_hits_as_int: true, diff --git a/x-pack/legacy/plugins/license_management/__jest__/__snapshots__/license_status.test.js.snap b/x-pack/legacy/plugins/license_management/__jest__/__snapshots__/license_status.test.js.snap index ee9abb1d1f125..b0bd05349a50c 100644 --- a/x-pack/legacy/plugins/license_management/__jest__/__snapshots__/license_status.test.js.snap +++ b/x-pack/legacy/plugins/license_management/__jest__/__snapshots__/license_status.test.js.snap @@ -1,5 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`LicenseStatus component should display display warning is expired 1`] = `"

Your Platinum license has expired

Your license expired on
"`; +exports[`LicenseStatus component should display display warning is expired 1`] = `"

Your Platinum license has expired

Your license expired on
"`; -exports[`LicenseStatus component should display normally when license is active 1`] = `"

Your Gold license is active

Your license will expire on October 12, 2099 7:00 PM EST
"`; +exports[`LicenseStatus component should display normally when license is active 1`] = `"

Your Gold license is active

Your license will expire on October 12, 2099 7:00 PM EST
"`; diff --git a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/license_status/license_status.js b/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/license_status/license_status.js index 9f6d1226e4dbf..0b7867ba45e2a 100644 --- a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/license_status/license_status.js +++ b/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/license_status/license_status.js @@ -87,7 +87,7 @@ export class LicenseStatus extends React.PureComponent { {icon} -

{title}

+

{title}

diff --git a/x-pack/legacy/plugins/maps/common/constants.js b/x-pack/legacy/plugins/maps/common/constants.js index 1fd1f4b43bbda..942d0a21123c2 100644 --- a/x-pack/legacy/plugins/maps/common/constants.js +++ b/x-pack/legacy/plugins/maps/common/constants.js @@ -102,3 +102,12 @@ export const DRAW_TYPE = { BOUNDS: 'BOUNDS', POLYGON: 'POLYGON' }; + +export const METRIC_TYPE = { + AVG: 'avg', + COUNT: 'count', + MAX: 'max', + MIN: 'min', + SUM: 'sum', + UNIQUE_COUNT: 'cardinality', +}; diff --git a/x-pack/legacy/plugins/maps/index.js b/x-pack/legacy/plugins/maps/index.js index 076fae1bb5882..3bb9d48741ab9 100644 --- a/x-pack/legacy/plugins/maps/index.js +++ b/x-pack/legacy/plugins/maps/index.js @@ -23,7 +23,8 @@ import _ from 'lodash'; export function maps(kibana) { return new kibana.Plugin({ - require: ['kibana', 'elasticsearch', 'xpack_main', 'tile_map', 'task_manager'], + // task_manager could be required, but is only used for telemetry + require: ['kibana', 'elasticsearch', 'xpack_main', 'tile_map'], id: APP_ID, configPrefix: 'xpack.maps', publicDir: resolve(__dirname, 'public'), diff --git a/x-pack/legacy/plugins/maps/public/components/metric_editor.js b/x-pack/legacy/plugins/maps/public/components/metric_editor.js index f3123d5645ccd..03d364f3d84a9 100644 --- a/x-pack/legacy/plugins/maps/public/components/metric_editor.js +++ b/x-pack/legacy/plugins/maps/public/components/metric_editor.js @@ -12,6 +12,7 @@ import { EuiFieldText, EuiFormRow } from '@elastic/eui'; import { MetricSelect, METRIC_AGGREGATION_VALUES } from './metric_select'; import { SingleFieldSelect } from './single_field_select'; +import { METRIC_TYPE } from '../../common/constants'; export function MetricEditor({ fields, metricsFilter, metric, onChange, removeButton }) { const onAggChange = metricAggregationType => { @@ -34,10 +35,12 @@ export function MetricEditor({ fields, metricsFilter, metric, onChange, removeBu }; let fieldSelect; - if (metric.type && metric.type !== 'count') { - const filterNumberFields = field => { - return field.type === 'number'; - }; + if (metric.type && metric.type !== METRIC_TYPE.COUNT) { + const filterField = metric.type !== METRIC_TYPE.UNIQUE_COUNT + ? field => { + return field.type === 'number'; + } + : undefined; fieldSelect = ( { - if (type === 'count') { + if (type === METRIC_TYPE.COUNT) { return true; } @@ -69,7 +71,7 @@ export class MetricsExpression extends Component { }) .map(({ type, field }) => { // do not use metric label so field and aggregation are not obscured. - if (type === 'count') { + if (type === METRIC_TYPE.COUNT) { return 'count'; } @@ -127,6 +129,6 @@ MetricsExpression.propTypes = { MetricsExpression.defaultProps = { metrics: [ - { type: 'count' } + { type: METRIC_TYPE.COUNT } ] }; diff --git a/x-pack/legacy/plugins/maps/public/index.js b/x-pack/legacy/plugins/maps/public/index.js index 5cd2288bf6a1e..49d8646c6a251 100644 --- a/x-pack/legacy/plugins/maps/public/index.js +++ b/x-pack/legacy/plugins/maps/public/index.js @@ -23,7 +23,6 @@ import routes from 'ui/routes'; import 'ui/kbn_top_nav'; import { uiModules } from 'ui/modules'; import { docTitle } from 'ui/doc_title'; -import 'ui/autoload/styles'; import 'ui/autoload/all'; import 'react-vis/dist/style.css'; diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js index 8416ef5709e30..776980e17bb13 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js @@ -21,7 +21,7 @@ import { RENDER_AS } from './render_as'; import { CreateSourceEditor } from './create_source_editor'; import { UpdateSourceEditor } from './update_source_editor'; import { GRID_RESOLUTION } from '../../grid_resolution'; -import { SOURCE_DATA_ID_ORIGIN, ES_GEO_GRID } from '../../../../common/constants'; +import { SOURCE_DATA_ID_ORIGIN, ES_GEO_GRID, METRIC_TYPE } from '../../../../common/constants'; import { i18n } from '@kbn/i18n'; import { getDataSourceLabel } from '../../../../common/i18n_getters'; @@ -36,9 +36,16 @@ const aggSchemas = new Schemas([ title: 'Value', min: 1, max: Infinity, - aggFilter: ['avg', 'count', 'max', 'min', 'sum'], + aggFilter: [ + METRIC_TYPE.AVG, + METRIC_TYPE.COUNT, + METRIC_TYPE.MAX, + METRIC_TYPE.MIN, + METRIC_TYPE.SUM, + METRIC_TYPE.UNIQUE_COUNT + ], defaults: [ - { schema: 'metric', type: 'count' } + { schema: 'metric', type: METRIC_TYPE.COUNT } ] }, { @@ -215,11 +222,11 @@ export class ESGeoGridSource extends AbstractESSource { } _formatMetricKey(metric) { - return metric.type !== 'count' ? `${metric.type}_of_${metric.field}` : COUNT_PROP_NAME; + return metric.type !== METRIC_TYPE.COUNT ? `${metric.type}_of_${metric.field}` : COUNT_PROP_NAME; } _formatMetricLabel(metric) { - return metric.type !== 'count' ? `${metric.type} of ${metric.field}` : COUNT_PROP_LABEL; + return metric.type !== METRIC_TYPE.COUNT ? `${metric.type} of ${metric.field}` : COUNT_PROP_LABEL; } _makeAggConfigs(precision) { @@ -231,7 +238,7 @@ export class ESGeoGridSource extends AbstractESSource { schema: 'metric', params: {} }; - if (metric.type !== 'count') { + if (metric.type !== METRIC_TYPE.COUNT) { metricAggConfig.params = { field: metric.field }; } return metricAggConfig; diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/update_source_editor.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/update_source_editor.js index bed8236da1be8..9ea5093724ed9 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/update_source_editor.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/update_source_editor.js @@ -8,6 +8,7 @@ import React, { Fragment, Component } from 'react'; import { RENDER_AS } from './render_as'; import { MetricsEditor } from '../../../components/metrics_editor'; +import { METRIC_TYPE } from '../../../../common/constants'; import { indexPatternService } from '../../../kibana_services'; import { ResolutionEditor } from './resolution_editor'; import { i18n } from '@kbn/i18n'; @@ -66,7 +67,7 @@ export class UpdateSourceEditor extends Component { this.props.renderAs === RENDER_AS.HEATMAP ? metric => { //these are countable metrics, where blending heatmap color blobs make sense - return ['count', 'sum'].includes(metric.value); + return [METRIC_TYPE.COUNT, METRIC_TYPE.SUM, METRIC_TYPE.UNIQUE_COUNT].includes(metric.value); } : null; const allowMultipleMetrics = this.props.renderAs !== RENDER_AS.HEATMAP; diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js index 4c081d386b3d7..3debfdf1541f7 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js @@ -15,7 +15,7 @@ import { UpdateSourceEditor } from './update_source_editor'; import { VectorStyle } from '../../styles/vector_style'; import { vectorStyles } from '../../styles/vector_style_defaults'; import { i18n } from '@kbn/i18n'; -import { SOURCE_DATA_ID_ORIGIN, ES_PEW_PEW } from '../../../../common/constants'; +import { SOURCE_DATA_ID_ORIGIN, ES_PEW_PEW, METRIC_TYPE } from '../../../../common/constants'; import { getDataSourceLabel } from '../../../../common/i18n_getters'; import { convertToLines } from './convert_to_lines'; import { Schemas } from 'ui/vis/editors/default/schemas'; @@ -32,9 +32,16 @@ const aggSchemas = new Schemas([ title: 'Value', min: 1, max: Infinity, - aggFilter: ['avg', 'count', 'max', 'min', 'sum'], + aggFilter: [ + METRIC_TYPE.AVG, + METRIC_TYPE.COUNT, + METRIC_TYPE.MAX, + METRIC_TYPE.MIN, + METRIC_TYPE.SUM, + METRIC_TYPE.UNIQUE_COUNT + ], defaults: [ - { schema: 'metric', type: 'count' } + { schema: 'metric', type: METRIC_TYPE.COUNT } ] } ]); @@ -193,7 +200,7 @@ export class ESPewPewSource extends AbstractESSource { schema: 'metric', params: {} }; - if (metric.type !== 'count') { + if (metric.type !== METRIC_TYPE.COUNT) { metricAggConfig.params = { field: metric.field }; } return metricAggConfig; @@ -252,11 +259,11 @@ export class ESPewPewSource extends AbstractESSource { } _formatMetricKey(metric) { - return metric.type !== 'count' ? `${metric.type}_of_${metric.field}` : COUNT_PROP_NAME; + return metric.type !== METRIC_TYPE.COUNT ? `${metric.type}_of_${metric.field}` : COUNT_PROP_NAME; } _formatMetricLabel(metric) { - return metric.type !== 'count' ? `${metric.type} of ${metric.field}` : COUNT_PROP_LABEL; + return metric.type !== METRIC_TYPE.COUNT ? `${metric.type} of ${metric.field}` : COUNT_PROP_LABEL; } async _getGeoField() { diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.js index 7c248e332d403..d9c48424bb77d 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.js @@ -512,9 +512,4 @@ export class ESSearchSource extends AbstractESSource { path: geoField.name, }; } - - _getRawFieldName(fieldName) { - // fieldName is rawFieldName for documents source since the source uses raw documents instead of aggregated metrics - return fieldName; - } } diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js index 0670474df89bb..85c866479a6ba 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js @@ -19,7 +19,7 @@ import { ESAggMetricTooltipProperty } from '../tooltips/es_aggmetric_tooltip_pro import uuid from 'uuid/v4'; import { copyPersistentState } from '../../reducers/util'; -import { ES_GEO_FIELD_TYPE } from '../../../common/constants'; +import { ES_GEO_FIELD_TYPE, METRIC_TYPE } from '../../../common/constants'; import { DataRequestAbortError } from '../util/data_request'; export class AbstractESSource extends AbstractVectorSource { @@ -59,7 +59,7 @@ export class AbstractESSource extends AbstractVectorSource { _getValidMetrics() { const metrics = _.get(this._descriptor, 'metrics', []).filter(({ type, field }) => { - if (type === 'count') { + if (type === METRIC_TYPE.COUNT) { return true; } @@ -69,7 +69,7 @@ export class AbstractESSource extends AbstractVectorSource { return false; }); if (metrics.length === 0) { - metrics.push({ type: 'count' }); + metrics.push({ type: METRIC_TYPE.COUNT }); } return metrics; } @@ -300,18 +300,13 @@ export class AbstractESSource extends AbstractVectorSource { return this._descriptor.id; } - _getRawFieldName(fieldName) { + async getFieldFormatter(fieldName) { const metricField = this.getMetricFields().find(({ propertyKey }) => { return propertyKey === fieldName; }); - return metricField ? metricField.field : null; - } - - async getFieldFormatter(fieldName) { - // fieldName could be an aggregation so it needs to be unpacked to expose raw field. - const rawFieldName = this._getRawFieldName(fieldName); - if (!rawFieldName) { + // Do not use field formatters for counting metrics + if (metricField && metricField.type === METRIC_TYPE.COUNT || metricField.type === METRIC_TYPE.UNIQUE_COUNT) { return null; } @@ -322,7 +317,10 @@ export class AbstractESSource extends AbstractVectorSource { return null; } - const fieldFromIndexPattern = indexPattern.fields.getByName(rawFieldName); + const realFieldName = metricField + ? metricField.field + : fieldName; + const fieldFromIndexPattern = indexPattern.fields.getByName(realFieldName); if (!fieldFromIndexPattern) { return null; } diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.js index 5d876dbbd011f..1f5adc00cca6f 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.js @@ -11,7 +11,7 @@ import { Schemas } from 'ui/vis/editors/default/schemas'; import { AggConfigs } from 'ui/agg_types'; import { i18n } from '@kbn/i18n'; import { ESTooltipProperty } from '../tooltips/es_tooltip_property'; -import { ES_SIZE_LIMIT } from '../../../common/constants'; +import { ES_SIZE_LIMIT, METRIC_TYPE } from '../../../common/constants'; const TERMS_AGG_NAME = 'join'; @@ -22,9 +22,16 @@ const aggSchemas = new Schemas([ title: 'Value', min: 1, max: Infinity, - aggFilter: ['avg', 'count', 'max', 'min', 'sum'], + aggFilter: [ + METRIC_TYPE.AVG, + METRIC_TYPE.COUNT, + METRIC_TYPE.MAX, + METRIC_TYPE.MIN, + METRIC_TYPE.SUM, + METRIC_TYPE.UNIQUE_COUNT + ], defaults: [ - { schema: 'metric', type: 'count' } + { schema: 'metric', type: METRIC_TYPE.COUNT } ] }, { @@ -81,12 +88,12 @@ export class ESTermSource extends AbstractESSource { } _formatMetricKey(metric) { - const metricKey = metric.type !== 'count' ? `${metric.type}_of_${metric.field}` : metric.type; + const metricKey = metric.type !== METRIC_TYPE.COUNT ? `${metric.type}_of_${metric.field}` : metric.type; return `__kbnjoin__${metricKey}_groupby_${this._descriptor.indexPatternTitle}.${this._descriptor.term}`; } _formatMetricLabel(metric) { - const metricLabel = metric.type !== 'count' ? `${metric.type} ${metric.field}` : 'count'; + const metricLabel = metric.type !== METRIC_TYPE.COUNT ? `${metric.type} ${metric.field}` : 'count'; return `${metricLabel} of ${this._descriptor.indexPatternTitle}:${this._descriptor.term}`; } @@ -108,13 +115,13 @@ export class ESTermSource extends AbstractESSource { const metricPropertyNames = configStates .filter(configState => { - return configState.schema === 'metric' && configState.type !== 'count'; + return configState.schema === 'metric' && configState.type !== METRIC_TYPE.COUNT; }) .map(configState => { return configState.id; }); const countConfigState = configStates.find(configState => { - return configState.type === 'count'; + return configState.type === METRIC_TYPE.COUNT; }); const countPropertyName = _.get(countConfigState, 'id'); return { @@ -128,7 +135,7 @@ export class ESTermSource extends AbstractESSource { _getRequestDescription(leftSourceName, leftFieldName) { const metrics = this._getValidMetrics().map(metric => { - return metric.type !== 'count' ? `${metric.type} ${metric.field}` : 'count'; + return metric.type !== METRIC_TYPE.COUNT ? `${metric.type} ${metric.field}` : 'count'; }); const joinStatement = []; joinStatement.push(i18n.translate('xpack.maps.source.esJoin.joinLeftDescription', { @@ -157,7 +164,7 @@ export class ESTermSource extends AbstractESSource { schema: 'metric', params: {} }; - if (metric.type !== 'count') { + if (metric.type !== METRIC_TYPE.COUNT) { metricAggConfig.params = { field: metric.field }; } return metricAggConfig; diff --git a/x-pack/legacy/plugins/maps/public/layers/tooltips/es_aggmetric_tooltip_property.js b/x-pack/legacy/plugins/maps/public/layers/tooltips/es_aggmetric_tooltip_property.js index 11cbb36f49593..42629e192c27d 100644 --- a/x-pack/legacy/plugins/maps/public/layers/tooltips/es_aggmetric_tooltip_property.js +++ b/x-pack/legacy/plugins/maps/public/layers/tooltips/es_aggmetric_tooltip_property.js @@ -6,6 +6,7 @@ import { ESTooltipProperty } from './es_tooltip_property'; +import { METRIC_TYPE } from '../../../common/constants'; export class ESAggMetricTooltipProperty extends ESTooltipProperty { @@ -21,7 +22,7 @@ export class ESAggMetricTooltipProperty extends ESTooltipProperty { if (typeof this._rawValue === 'undefined') { return '-'; } - if (this._metricField.type === 'count') { + if (this._metricField.type === METRIC_TYPE.COUNT || this._metricField.type === METRIC_TYPE.UNIQUE_COUNT) { return this._rawValue; } const indexPatternField = this._indexPattern.fields.getByName(this._metricField.field); diff --git a/x-pack/legacy/plugins/maps/server/maps_telemetry/maps_usage_collector.js b/x-pack/legacy/plugins/maps/server/maps_telemetry/maps_usage_collector.js index 5f6361a16aa00..c0ac5a781b796 100644 --- a/x-pack/legacy/plugins/maps/server/maps_telemetry/maps_usage_collector.js +++ b/x-pack/legacy/plugins/maps/server/maps_telemetry/maps_usage_collector.js @@ -9,7 +9,7 @@ import { TASK_ID, scheduleTask, registerMapsTelemetryTask } from './telemetry_ta export function initTelemetryCollection(server) { registerMapsTelemetryTask(server); - scheduleTask(server, server.plugins.task_manager); + scheduleTask(server); registerMapsUsageCollector(server); } @@ -21,6 +21,11 @@ async function isTaskManagerReady(server) { async function fetch(server) { let docs; const taskManager = server.plugins.task_manager; + + if (!taskManager) { + return null; + } + try { ({ docs } = await taskManager.fetch({ query: { diff --git a/x-pack/legacy/plugins/maps/server/maps_telemetry/telemetry_task.js b/x-pack/legacy/plugins/maps/server/maps_telemetry/telemetry_task.js index 521c5ae71e14b..3702bc8e29539 100644 --- a/x-pack/legacy/plugins/maps/server/maps_telemetry/telemetry_task.js +++ b/x-pack/legacy/plugins/maps/server/maps_telemetry/telemetry_task.js @@ -10,7 +10,14 @@ const TELEMETRY_TASK_TYPE = 'maps_telemetry'; export const TASK_ID = `Maps-${TELEMETRY_TASK_TYPE}`; -export function scheduleTask(server, taskManager) { +export function scheduleTask(server) { + const taskManager = server.plugins.task_manager; + + if (!taskManager) { + server.log(['debug', 'telemetry'], `Task manager is not available`); + return; + } + const { kbnServer } = server.plugins.xpack_main.status.plugin; kbnServer.afterPluginsInit(() => { @@ -36,6 +43,12 @@ export function scheduleTask(server, taskManager) { export function registerMapsTelemetryTask(server) { const taskManager = server.plugins.task_manager; + + if (!taskManager) { + server.log(['debug', 'telemetry'], `Task manager is not available`); + return; + } + taskManager.registerTaskDefinitions({ [TELEMETRY_TASK_TYPE]: { title: 'Maps telemetry fetch task', diff --git a/x-pack/legacy/plugins/ml/public/components/chart_tooltip/chart_tooltip.tsx b/x-pack/legacy/plugins/ml/public/components/chart_tooltip/chart_tooltip.tsx index 784deb260902b..ea9bc4f0f92ee 100644 --- a/x-pack/legacy/plugins/ml/public/components/chart_tooltip/chart_tooltip.tsx +++ b/x-pack/legacy/plugins/ml/public/components/chart_tooltip/chart_tooltip.tsx @@ -12,7 +12,53 @@ import { TooltipValueFormatter } from '@elastic/charts'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { useObservable } from '../../../../../../../src/plugins/kibana_react/public/util/use_observable'; -import { chartTooltip$, mlChartTooltipService, ChartTooltipValue } from './chart_tooltip_service'; +import { chartTooltip$, ChartTooltipValue } from './chart_tooltip_service'; + +type RefValue = HTMLElement | null; + +function useRefWithCallback() { + const chartTooltipState = useObservable(chartTooltip$); + const ref = useRef(null); + + return (node: RefValue) => { + ref.current = node; + + if ( + node !== null && + node.parentElement !== null && + chartTooltipState !== undefined && + chartTooltipState.isTooltipVisible + ) { + const parentBounding = node.parentElement.getBoundingClientRect(); + + const { targetPosition, offset } = chartTooltipState; + + const contentWidth = document.body.clientWidth - parentBounding.left; + const tooltipWidth = node.clientWidth; + + let left = targetPosition.left + offset.x - parentBounding.left; + if (left + tooltipWidth > contentWidth) { + // the tooltip is hanging off the side of the page, + // so move it to the other side of the target + const markerWidthAdjustment = 25; + left = left - (tooltipWidth + offset.x + markerWidthAdjustment); + } + + const top = targetPosition.top + offset.y - parentBounding.top; + + if ( + chartTooltipState.tooltipPosition.left !== left || + chartTooltipState.tooltipPosition.top !== top + ) { + // render the tooltip with adjusted position. + chartTooltip$.next({ + ...chartTooltipState, + tooltipPosition: { left, top }, + }); + } + } + }; +} const renderHeader = (headerData?: ChartTooltipValue, formatter?: TooltipValueFormatter) => { if (!headerData) { @@ -23,24 +69,18 @@ const renderHeader = (headerData?: ChartTooltipValue, formatter?: TooltipValueFo }; export const ChartTooltip: FC = () => { - const chartTooltipElement = useRef(null); - - mlChartTooltipService.element = chartTooltipElement.current; - const chartTooltipState = useObservable(chartTooltip$); + const chartTooltipElement = useRefWithCallback(); if (chartTooltipState === undefined || !chartTooltipState.isTooltipVisible) { return
; } const { tooltipData, tooltipHeaderFormatter, tooltipPosition } = chartTooltipState; + const transform = `translate(${tooltipPosition.left}px, ${tooltipPosition.top}px)`; return ( -
+
{tooltipData.length > 0 && tooltipData[0].skipHeader === undefined && (
{renderHeader(tooltipData[0], tooltipHeaderFormatter)} diff --git a/x-pack/legacy/plugins/ml/public/components/chart_tooltip/chart_tooltip_service.d.ts b/x-pack/legacy/plugins/ml/public/components/chart_tooltip/chart_tooltip_service.d.ts index 2e80d577adeb6..e6b0b6b4270bd 100644 --- a/x-pack/legacy/plugins/ml/public/components/chart_tooltip/chart_tooltip_service.d.ts +++ b/x-pack/legacy/plugins/ml/public/components/chart_tooltip/chart_tooltip_service.d.ts @@ -8,15 +8,19 @@ import { BehaviorSubject } from 'rxjs'; import { TooltipValue, TooltipValueFormatter } from '@elastic/charts'; +export declare const getChartTooltipDefaultState: () => ChartTooltipState; + export interface ChartTooltipValue extends TooltipValue { skipHeader?: boolean; } interface ChartTooltipState { isTooltipVisible: boolean; + offset: ToolTipOffset; + targetPosition: ClientRect; tooltipData: ChartTooltipValue[]; tooltipHeaderFormatter?: TooltipValueFormatter; - tooltipPosition: { transform: string }; + tooltipPosition: { left: number; top: number }; } export declare const chartTooltip$: BehaviorSubject; @@ -27,11 +31,10 @@ interface ToolTipOffset { } interface MlChartTooltipService { - element: HTMLElement | null; show: ( tooltipData: ChartTooltipValue[], - target: HTMLElement | null, - offset: ToolTipOffset + target?: HTMLElement | null, + offset?: ToolTipOffset ) => void; hide: () => void; } diff --git a/x-pack/legacy/plugins/ml/public/components/chart_tooltip/chart_tooltip_service.js b/x-pack/legacy/plugins/ml/public/components/chart_tooltip/chart_tooltip_service.js index 2177ed9fa2288..8fe1e795df53a 100644 --- a/x-pack/legacy/plugins/ml/public/components/chart_tooltip/chart_tooltip_service.js +++ b/x-pack/legacy/plugins/ml/public/components/chart_tooltip/chart_tooltip_service.js @@ -6,74 +6,32 @@ import { BehaviorSubject } from 'rxjs'; -const doc = document.documentElement; - -const chartTooltipDefaultState = { +export const getChartTooltipDefaultState = () => ({ isTooltipVisible: false, tooltipData: [], - tooltipPosition: { transform: 'translate(0px, 0px)' } -}; + offset: { x: 0, y: 0 }, + targetPosition: { left: 0, top: 0 }, + tooltipPosition: { left: 0, top: 0 } +}); -export const chartTooltip$ = new BehaviorSubject(chartTooltipDefaultState); +export const chartTooltip$ = new BehaviorSubject(getChartTooltipDefaultState()); export const mlChartTooltipService = { - element: null, -}; - -mlChartTooltipService.show = function (tooltipData, target, offset = { x: 0, y: 0 }) { - if (this.element === null || typeof target === 'undefined') { - return; - } - - // side bar width - const euiNavDrawer = document.getElementsByClassName('euiNavDrawer'); - - if (euiNavDrawer.length === 0) { - return; - } - - // enable the tooltip to render it in the DOM - // so the correct `tooltipWidth` gets returned. - const tooltipState = { - ...chartTooltipDefaultState, - isTooltipVisible: true, - tooltipData, - }; - chartTooltip$.next(tooltipState); - - const navOffset = euiNavDrawer[0].clientWidth; // Offset by width of side navbar - const contentWidth = document.body.clientWidth - navOffset; - const tooltipWidth = this.element.clientWidth; - - const pos = target.getBoundingClientRect(); - let left = pos.left + offset.x + 4 - navOffset; - if (left + tooltipWidth > contentWidth) { - // the tooltip is hanging off the side of the page, - // so move it to the other side of the target - const markerWidthAdjustment = 10; - left = left - (tooltipWidth + offset.x + markerWidthAdjustment); + show: (tooltipData, target, offset = { x: 0, y: 0 }) => { + if (typeof target !== 'undefined' && target !== null) { + chartTooltip$.next({ + ...chartTooltip$.getValue(), + isTooltipVisible: true, + offset, + targetPosition: target.getBoundingClientRect(), + tooltipData, + }); + } + }, + hide: () => { + chartTooltip$.next({ + ...getChartTooltipDefaultState(), + isTooltipVisible: false + }); } - - // Calculate top offset - const scrollTop = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0); - const topNavHeightAdjustment = 190; - const top = pos.top + offset.y + scrollTop - topNavHeightAdjustment; - - // render the tooltip with adjusted position. - chartTooltip$.next({ - ...tooltipState, - tooltipPosition: { transform: `translate(${left}px, ${top}px)` } - }); - -}; - -// When selecting multiple cells using dragSelect, we need to quickly -// hide the tooltip with `noTransition`, otherwise, if the mouse pointer -// enters the tooltip while dragging, it will cancel selecting multiple -// swimlane cells which we'd like to avoid of course. -mlChartTooltipService.hide = function () { - chartTooltip$.next({ - ...chartTooltipDefaultState, - isTooltipVisible: false - }); }; diff --git a/x-pack/legacy/plugins/ml/public/components/chart_tooltip/__tests__/chart_tooltip.js b/x-pack/legacy/plugins/ml/public/components/chart_tooltip/chart_tooltip_service.test.ts similarity index 50% rename from x-pack/legacy/plugins/ml/public/components/chart_tooltip/__tests__/chart_tooltip.js rename to x-pack/legacy/plugins/ml/public/components/chart_tooltip/chart_tooltip_service.test.ts index a542b5284b70b..aa1dbf92b0677 100644 --- a/x-pack/legacy/plugins/ml/public/components/chart_tooltip/__tests__/chart_tooltip.js +++ b/x-pack/legacy/plugins/ml/public/components/chart_tooltip/chart_tooltip_service.test.ts @@ -4,21 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import expect from '@kbn/expect'; - -import { mlChartTooltipService } from '../chart_tooltip_service'; +import { getChartTooltipDefaultState, mlChartTooltipService } from './chart_tooltip_service'; describe('ML - mlChartTooltipService', () => { it('service API duck typing', () => { - expect(mlChartTooltipService).to.be.an('object'); - expect(mlChartTooltipService.show).to.be.a('function'); - expect(mlChartTooltipService.hide).to.be.a('function'); + expect(typeof mlChartTooltipService).toBe('object'); + expect(typeof mlChartTooltipService.show).toBe('function'); + expect(typeof mlChartTooltipService.hide).toBe('function'); }); it('should fail silently when target is not defined', () => { - mlChartTooltipService.element = {}; expect(() => { - mlChartTooltipService.show('', undefined); - }).to.not.throwError('Call to show() should fail silently.'); + mlChartTooltipService.show(getChartTooltipDefaultState().tooltipData, null); + }).not.toThrow('Call to show() should fail silently.'); }); }); diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row_json_pane.tsx b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row_json_pane.tsx index d0e23f96de049..ac7fdcb129531 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row_json_pane.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row_json_pane.tsx @@ -28,6 +28,7 @@ export const ExpandedRowJsonPane: FC = ({ json }) => { readOnly={true} mode="json" style={{ width: '100%' }} + theme="textmate" />   diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/create_analytics_advanced_editor/create_analytics_advanced_editor.tsx b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/create_analytics_advanced_editor/create_analytics_advanced_editor.tsx index 9a5344cd1f984..daf21d57b0510 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/create_analytics_advanced_editor/create_analytics_advanced_editor.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/create_analytics_advanced_editor/create_analytics_advanced_editor.tsx @@ -127,6 +127,7 @@ export const CreateAnalyticsAdvancedEditor: FC = ({ ac fontSize: '12px', maxLines: 20, }} + theme="textmate" aria-label={i18n.translate( 'xpack.ml.dataframe.analytics.create.advancedEditor.codeEditorAriaLabel', { diff --git a/x-pack/legacy/plugins/ml/public/explorer/explorer.js b/x-pack/legacy/plugins/ml/public/explorer/explorer.js index 694e61db0583e..1acdd041c4052 100644 --- a/x-pack/legacy/plugins/ml/public/explorer/explorer.js +++ b/x-pack/legacy/plugins/ml/public/explorer/explorer.js @@ -1169,8 +1169,9 @@ export const Explorer = injectI18n(injectObservablesAsProps( return ( -
+ {/* Make sure ChartTooltip is inside this plain wrapping div so positioning can be infered correctly. */} + {noInfluencersConfigured === false && influencers !== undefined && diff --git a/x-pack/legacy/plugins/ml/public/explorer/explorer_charts/explorer_chart_distribution.js b/x-pack/legacy/plugins/ml/public/explorer/explorer_charts/explorer_chart_distribution.js index 82a4744f6aec8..588c3e3d6f1e9 100644 --- a/x-pack/legacy/plugins/ml/public/explorer/explorer_charts/explorer_chart_distribution.js +++ b/x-pack/legacy/plugins/ml/public/explorer/explorer_charts/explorer_chart_distribution.js @@ -498,8 +498,8 @@ export const ExplorerChartDistribution = injectI18n(class ExplorerChartDistribut } mlChartTooltipService.show(tooltipData, circle, { - x: LINE_CHART_ANOMALY_RADIUS * 2, - y: 0 + x: LINE_CHART_ANOMALY_RADIUS * 3, + y: LINE_CHART_ANOMALY_RADIUS * 2, }); } } diff --git a/x-pack/legacy/plugins/ml/public/explorer/explorer_charts/explorer_chart_single_metric.js b/x-pack/legacy/plugins/ml/public/explorer/explorer_charts/explorer_chart_single_metric.js index 2d991172ed345..be85af5a70c40 100644 --- a/x-pack/legacy/plugins/ml/public/explorer/explorer_charts/explorer_chart_single_metric.js +++ b/x-pack/legacy/plugins/ml/public/explorer/explorer_charts/explorer_chart_single_metric.js @@ -444,8 +444,8 @@ export const ExplorerChartSingleMetric = injectI18n(class ExplorerChartSingleMet } mlChartTooltipService.show(tooltipData, circle, { - x: LINE_CHART_ANOMALY_RADIUS * 2, - y: 0 + x: LINE_CHART_ANOMALY_RADIUS * 3, + y: LINE_CHART_ANOMALY_RADIUS * 2, }); } } diff --git a/x-pack/legacy/plugins/ml/public/explorer/explorer_swimlane.js b/x-pack/legacy/plugins/ml/public/explorer/explorer_swimlane.js index d49a1dc0523c4..2ee725b6fda86 100644 --- a/x-pack/legacy/plugins/ml/public/explorer/explorer_swimlane.js +++ b/x-pack/legacy/plugins/ml/public/explorer/explorer_swimlane.js @@ -325,10 +325,10 @@ export const ExplorerSwimlane = injectI18n(class ExplorerSwimlane extends React. yAccessor: 'anomaly_score' }); - const offsets = (target.className === 'sl-cell-inner' ? { x: 0, y: 0 } : { x: 2, y: 1 }); + const offsets = (target.className === 'sl-cell-inner' ? { x: 6, y: 0 } : { x: 8, y: 1 }); mlChartTooltipService.show(tooltipData, target, { - x: target.offsetWidth - offsets.x, - y: 10 + offsets.y + x: target.offsetWidth + offsets.x, + y: 6 + offsets.y }); } @@ -364,7 +364,7 @@ export const ExplorerSwimlane = injectI18n(class ExplorerSwimlane extends React. .on('mouseover', label => { mlChartTooltipService.show([{ skipHeader: true }, { name: swimlaneData.fieldName, value: label }], this, { x: laneLabelWidth, - y: 8 + y: 0 }); }) .on('mouseout', () => { diff --git a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/ml_job_editor/ml_job_editor.js b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/ml_job_editor/ml_job_editor.js index 22d45a70075e9..39cd21688dec5 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/ml_job_editor/ml_job_editor.js +++ b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/ml_job_editor/ml_job_editor.js @@ -21,6 +21,7 @@ export function MLJobEditor({ mode = EDITOR_MODE.JSON, readOnly = false, syntaxChecking = true, + theme = 'textmate', onChange = () => {} }) { return ( @@ -32,6 +33,7 @@ export function MLJobEditor({ readOnly={readOnly} wrapEnabled={true} showPrintMargin={false} + theme={theme} editorProps={{ $blockScrolling: true }} setOptions={{ useWorker: syntaxChecking, @@ -48,5 +50,7 @@ MLJobEditor.propTypes = { width: PropTypes.string, mode: PropTypes.string, readOnly: PropTypes.bool, + syntaxChecking: PropTypes.bool, + theme: PropTypes.string, onChange: PropTypes.func, }; diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_creator/util/general.ts b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_creator/util/general.ts index 10a9260598dea..71535ab98c74f 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_creator/util/general.ts +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/common/job_creator/util/general.ts @@ -77,10 +77,9 @@ export function getRichDetectors( }); } -export function createFieldOptions(fields: Field[], filterOverride?: (f: Field) => boolean) { - const filter = filterOverride || (f => f.id !== EVENT_RATE_FIELD_ID); +export function createFieldOptions(fields: Field[]) { return fields - .filter(filter) + .filter(f => f.id !== EVENT_RATE_FIELD_ID) .map(f => ({ label: f.name, })) diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/datafeed_step/components/time_field/time_field.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/datafeed_step/components/time_field/time_field.tsx index 71e48a7afb359..86005dd140fed 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/datafeed_step/components/time_field/time_field.tsx +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/datafeed_step/components/time_field/time_field.tsx @@ -15,7 +15,7 @@ import { Description } from './description'; export const TimeField: FC = () => { const { jobCreator: jc, jobCreatorUpdate, jobCreatorUpdated } = useContext(JobCreatorContext); const jobCreator = jc as AdvancedJobCreator; - const { fields } = newJobCapsService; + const { dateFields } = newJobCapsService; const [timeFieldName, setTimeFieldName] = useState(jobCreator.timeFieldName); useEffect(() => { @@ -30,7 +30,7 @@ export const TimeField: FC = () => { return ( diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/datafeed_step/components/time_field/time_field_select.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/datafeed_step/components/time_field/time_field_select.tsx index 25e462dfa2286..3b1993f8e2c7e 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/datafeed_step/components/time_field/time_field_select.tsx +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/datafeed_step/components/time_field/time_field_select.tsx @@ -7,8 +7,7 @@ import React, { FC } from 'react'; import { EuiComboBox, EuiComboBoxOptionProps } from '@elastic/eui'; -import { Field, EVENT_RATE_FIELD_ID } from '../../../../../../../../common/types/fields'; -import { ES_FIELD_TYPES } from '../../../../../../../../../../../../src/plugins/data/public'; +import { Field } from '../../../../../../../../common/types/fields'; import { createFieldOptions } from '../../../../../common/job_creator/util/general'; interface Props { @@ -18,16 +17,12 @@ interface Props { } export const TimeFieldSelect: FC = ({ fields, changeHandler, selectedField }) => { - const options: EuiComboBoxOptionProps[] = createFieldOptions( - fields, - f => f.id !== EVENT_RATE_FIELD_ID && f.type === ES_FIELD_TYPES.DATE - ); + const options: EuiComboBoxOptionProps[] = createFieldOptions(fields); - const selection: EuiComboBoxOptionProps[] = [ - { - label: selectedField !== null ? selectedField : '', - }, - ]; + const selection: EuiComboBoxOptionProps[] = []; + if (selectedField !== null) { + selection.push({ label: selectedField }); + } function onChange(selectedOptions: EuiComboBoxOptionProps[]) { const option = selectedOptions[0]; diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/advanced_detector_modal/advanced_detector_modal.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/advanced_detector_modal/advanced_detector_modal.tsx index fb1f71f0d298a..b9e9df77d35e3 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/advanced_detector_modal/advanced_detector_modal.tsx +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/advanced_detector_modal/advanced_detector_modal.tsx @@ -83,7 +83,7 @@ export const AdvancedDetectorModal: FC = ({ createExcludeFrequentOption(detector.excludeFrequent) ); const [descriptionOption, setDescriptionOption] = useState(detector.description || ''); - const [fieldsEnabled, setFieldsEnabled] = useState(true); + const [splitFieldsEnabled, setSplitFieldsEnabled] = useState(true); const [excludeFrequentEnabled, setExcludeFrequentEnabled] = useState(true); const [fieldOptionEnabled, setFieldOptionEnabled] = useState(true); const { descriptionPlaceholder, setDescriptionPlaceholder } = useDetectorPlaceholder(detector); @@ -104,12 +104,12 @@ export const AdvancedDetectorModal: FC = ({ const allFieldOptions: EuiComboBoxOptionProps[] = [ ...createFieldOptions(fields), ...createScriptFieldOptions(jobCreator.scriptFields), - ]; + ].sort(comboBoxOptionsSort); - const splitFieldOptions = [ + const splitFieldOptions: EuiComboBoxOptionProps[] = [ ...allFieldOptions, ...createMlcategoryFieldOption(jobCreator.categorizationFieldName), - ]; + ].sort(comboBoxOptionsSort); const eventRateField = fields.find(f => f.id === EVENT_RATE_FIELD_ID); @@ -139,20 +139,22 @@ export const AdvancedDetectorModal: FC = ({ const partitionField = getField(partitionFieldOption.label); if (agg !== null) { - setFieldsEnabled(true); setCurrentFieldOptions(agg); if (isFieldlessAgg(agg) && eventRateField !== undefined) { + setSplitFieldsEnabled(true); setFieldOption(emptyOption); setFieldOptionEnabled(false); field = eventRateField; } else { + setSplitFieldsEnabled(field !== null); setFieldOptionEnabled(true); - // only enable exclude frequent if there is a by or over selected - setExcludeFrequentEnabled(byField !== null || overField !== null); } + // only enable exclude frequent if there is a by or over selected + setExcludeFrequentEnabled(byField !== null || overField !== null); } else { - setFieldsEnabled(false); + setSplitFieldsEnabled(false); + setFieldOptionEnabled(false); } const dtr: RichDetector = { @@ -179,7 +181,7 @@ export const AdvancedDetectorModal: FC = ({ useEffect(() => { const agg = getAgg(aggOption.label); - setFieldsEnabled(aggOption.label !== ''); + setSplitFieldsEnabled(aggOption.label !== ''); if (agg !== null) { setFieldOptionEnabled(isFieldlessAgg(agg) === false); @@ -202,7 +204,7 @@ export const AdvancedDetectorModal: FC = ({ function saveEnabled() { return ( - fieldsEnabled && + splitFieldsEnabled && (fieldOptionEnabled === false || (fieldOptionEnabled === true && fieldOption.label !== '')) ); } @@ -216,7 +218,7 @@ export const AdvancedDetectorModal: FC = ({ @@ -230,7 +232,7 @@ export const AdvancedDetectorModal: FC = ({ selectedOptions={createSelectedOptions(fieldOption, currentFieldOptions)} onChange={onOptionChange(setFieldOption)} isClearable={true} - isDisabled={fieldsEnabled === false || fieldOptionEnabled === false} + isDisabled={fieldOptionEnabled === false} /> @@ -245,7 +247,7 @@ export const AdvancedDetectorModal: FC = ({ selectedOptions={createSelectedOptions(byFieldOption, splitFieldOptions)} onChange={onOptionChange(setByFieldOption)} isClearable={true} - isDisabled={fieldsEnabled === false} + isDisabled={splitFieldsEnabled === false} /> @@ -257,7 +259,7 @@ export const AdvancedDetectorModal: FC = ({ selectedOptions={createSelectedOptions(overFieldOption, splitFieldOptions)} onChange={onOptionChange(setOverFieldOption)} isClearable={true} - isDisabled={fieldsEnabled === false} + isDisabled={splitFieldsEnabled === false} /> @@ -269,7 +271,7 @@ export const AdvancedDetectorModal: FC = ({ selectedOptions={createSelectedOptions(partitionFieldOption, splitFieldOptions)} onChange={onOptionChange(setPartitionFieldOption)} isClearable={true} - isDisabled={fieldsEnabled === false} + isDisabled={splitFieldsEnabled === false} /> @@ -278,10 +280,13 @@ export const AdvancedDetectorModal: FC = ({ @@ -393,8 +398,15 @@ function createDefaultDescription(dtr: RichDetector) { // if the options list only contains one option and nothing has been selected, set // selectedOptions list to be an empty array function createSelectedOptions( - option: EuiComboBoxOptionProps, + selectedOption: EuiComboBoxOptionProps, options: EuiComboBoxOptionProps[] ): EuiComboBoxOptionProps[] { - return options.length === 1 && options[0].label !== option.label ? [] : [option]; + return (options.length === 1 && options[0].label !== selectedOption.label) || + selectedOption.label === '' + ? [] + : [selectedOption]; +} + +function comboBoxOptionsSort(a: EuiComboBoxOptionProps, b: EuiComboBoxOptionProps) { + return a.label.localeCompare(b.label); } diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/advanced_view/detector_list.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/advanced_view/detector_list.tsx index 2500cf79535d9..d11ef8164169a 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/advanced_view/detector_list.tsx +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/advanced_view/detector_list.tsx @@ -18,10 +18,12 @@ import { EuiSpacer, EuiCallOut, EuiHorizontalRule, + EuiFormRow, } from '@elastic/eui'; import { JobCreatorContext } from '../../../job_creator_context'; import { AdvancedJobCreator } from '../../../../../common/job_creator'; +import { Validation } from '../../../../../common/job_validator'; import { detectorToString } from '../../../../../../../util/string_utils'; interface Props { @@ -31,14 +33,21 @@ interface Props { } export const DetectorList: FC = ({ isActive, onEditJob, onDeleteJob }) => { - const { jobCreator: jc, jobCreatorUpdated } = useContext(JobCreatorContext); + const { jobCreator: jc, jobCreatorUpdated, jobValidator, jobValidatorUpdated } = useContext( + JobCreatorContext + ); const jobCreator = jc as AdvancedJobCreator; const [detectors, setDetectors] = useState(jobCreator.detectors); + const [validation, setValidation] = useState(jobValidator.duplicateDetectors); useEffect(() => { setDetectors(jobCreator.detectors); }, [jobCreatorUpdated]); + useEffect(() => { + setValidation(jobValidator.duplicateDetectors); + }, [jobValidatorUpdated]); + const Buttons: FC<{ index: number }> = ({ index }) => { return ( @@ -99,9 +108,11 @@ export const DetectorList: FC = ({ isActive, onEditJob, onDeleteJob }) => detectorToString(d) )} - - - + {isActive && ( + + + + )} {d.detector_description !== undefined && ( @@ -113,6 +124,7 @@ export const DetectorList: FC = ({ isActive, onEditJob, onDeleteJob }) => ))} + ); }; @@ -140,3 +152,17 @@ const NoDetectorsWarning: FC<{ show: boolean }> = ({ show }) => { ); }; + +const DuplicateDetectorsWarning: FC<{ validation: Validation }> = ({ validation }) => { + if (validation.valid === true) { + return null; + } + return ( + + + + + + + ); +}; diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/agg_select/agg_select.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/agg_select/agg_select.tsx index d205691fcb64b..bda29b244a5b1 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/agg_select/agg_select.tsx +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/agg_select/agg_select.tsx @@ -82,6 +82,6 @@ export const AggSelect: FC = ({ fields, changeHandler, selectedOptions, r ); }; -export function createLabel(pair: AggFieldPair | null): string { - return pair === null ? '' : `${pair.agg.title}(${pair.field.name})`; +export function createLabel(pair: AggFieldPair): string { + return `${pair.agg.title}(${pair.field.name})`; } diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/categorization_field/categorization_field.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/categorization_field/categorization_field.tsx index 996fa8cd3f862..f9edf79364c97 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/categorization_field/categorization_field.tsx +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/categorization_field/categorization_field.tsx @@ -19,7 +19,7 @@ import { Description } from './description'; export const CategorizationField: FC = () => { const { jobCreator: jc, jobCreatorUpdate, jobCreatorUpdated } = useContext(JobCreatorContext); const jobCreator = jc as MultiMetricJobCreator | PopulationJobCreator | AdvancedJobCreator; - const { fields } = newJobCapsService; + const { catFields } = newJobCapsService; const [categorizationFieldName, setCategorizationFieldName] = useState( jobCreator.categorizationFieldName ); @@ -36,7 +36,7 @@ export const CategorizationField: FC = () => { return ( diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/categorization_field/categorization_field_select.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/categorization_field/categorization_field_select.tsx index f9d99f7d0f4e0..f9fdba31a0ad4 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/categorization_field/categorization_field_select.tsx +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/categorization_field/categorization_field_select.tsx @@ -8,8 +8,7 @@ import React, { FC, useContext } from 'react'; import { EuiComboBox, EuiComboBoxOptionProps } from '@elastic/eui'; import { JobCreatorContext } from '../../../job_creator_context'; -import { Field, EVENT_RATE_FIELD_ID } from '../../../../../../../../common/types/fields'; -import { ES_FIELD_TYPES } from '../../../../../../../../../../../../src/plugins/data/public'; +import { Field } from '../../../../../../../../common/types/fields'; import { createFieldOptions, createScriptFieldOptions, @@ -24,18 +23,14 @@ interface Props { export const CategorizationFieldSelect: FC = ({ fields, changeHandler, selectedField }) => { const { jobCreator } = useContext(JobCreatorContext); const options: EuiComboBoxOptionProps[] = [ - ...createFieldOptions( - fields, - f => f.id !== EVENT_RATE_FIELD_ID && f.type === ES_FIELD_TYPES.KEYWORD - ), + ...createFieldOptions(fields), ...createScriptFieldOptions(jobCreator.scriptFields), ]; - const selection: EuiComboBoxOptionProps[] = [ - { - label: selectedField !== null ? selectedField : '', - }, - ]; + const selection: EuiComboBoxOptionProps[] = []; + if (selectedField !== null) { + selection.push({ label: selectedField }); + } function onChange(selectedOptions: EuiComboBoxOptionProps[]) { const option = selectedOptions[0]; diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/multi_metric_view/metric_selection.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/multi_metric_view/metric_selection.tsx index f913b5a5720d6..b0a5049924cbc 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/multi_metric_view/metric_selection.tsx +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/multi_metric_view/metric_selection.tsx @@ -33,7 +33,7 @@ export const MultiMetricDetectors: FC = ({ setIsValid }) => { const jobCreator = jc as MultiMetricJobCreator; const { fields } = newJobCapsService; - const [selectedOptions, setSelectedOptions] = useState([{ label: '' }]); + const [selectedOptions, setSelectedOptions] = useState([]); const [aggFieldPairList, setAggFieldPairList] = useState( jobCreator.aggFieldPairs ); @@ -57,7 +57,7 @@ export const MultiMetricDetectors: FC = ({ setIsValid }) => { if (typeof option !== 'undefined') { const newPair = { agg: option.agg, field: option.field }; setAggFieldPairList([...aggFieldPairList, newPair]); - setSelectedOptions([{ label: '' }]); + setSelectedOptions([]); } else { setAggFieldPairList([]); } diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/population_view/metric_selection.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/population_view/metric_selection.tsx index 87d3e87d65536..9a24381c9e35f 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/population_view/metric_selection.tsx +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/population_view/metric_selection.tsx @@ -36,7 +36,7 @@ export const PopulationDetectors: FC = ({ setIsValid }) => { const jobCreator = jc as PopulationJobCreator; const { fields } = newJobCapsService; - const [selectedOptions, setSelectedOptions] = useState([{ label: '' }]); + const [selectedOptions, setSelectedOptions] = useState([]); const [aggFieldPairList, setAggFieldPairList] = useState( jobCreator.aggFieldPairs ); @@ -62,7 +62,7 @@ export const PopulationDetectors: FC = ({ setIsValid }) => { if (typeof option !== 'undefined') { const newPair = { agg: option.agg, field: option.field, by: { field: null, value: null } }; setAggFieldPairList([...aggFieldPairList, newPair]); - setSelectedOptions([{ label: '' }]); + setSelectedOptions([]); } else { setAggFieldPairList([]); } diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/single_metric_view/metric_selection.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/single_metric_view/metric_selection.tsx index 45872faae4c09..19ccca44dc0a5 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/single_metric_view/metric_selection.tsx +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/single_metric_view/metric_selection.tsx @@ -32,9 +32,9 @@ export const SingleMetricDetectors: FC = ({ setIsValid }) => { const jobCreator = jc as SingleMetricJobCreator; const { fields } = newJobCapsService; - const [selectedOptions, setSelectedOptions] = useState([ - { label: createLabel(jobCreator.aggFieldPair) }, - ]); + const [selectedOptions, setSelectedOptions] = useState( + jobCreator.aggFieldPair !== null ? [{ label: createLabel(jobCreator.aggFieldPair) }] : [] + ); const [aggFieldPair, setAggFieldPair] = useState(jobCreator.aggFieldPair); const [lineChartsData, setLineChartData] = useState({}); const [loadingData, setLoadingData] = useState(false); diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/summary_count_field/summary_count_field_select.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/summary_count_field/summary_count_field_select.tsx index 6bf510a70bfcb..2f240344e0ea5 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/summary_count_field/summary_count_field_select.tsx +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/summary_count_field/summary_count_field_select.tsx @@ -27,11 +27,10 @@ export const SummaryCountFieldSelect: FC = ({ fields, changeHandler, sele ...createScriptFieldOptions(jobCreator.scriptFields), ]; - const selection: EuiComboBoxOptionProps[] = [ - { - label: selectedField !== null ? selectedField : '', - }, - ]; + const selection: EuiComboBoxOptionProps[] = []; + if (selectedField !== null) { + selection.push({ label: selectedField }); + } function onChange(selectedOptions: EuiComboBoxOptionProps[]) { const option = selectedOptions[0]; diff --git a/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_panel.tsx b/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_panel.tsx index ff7c519829b7c..fb08403b391ef 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_panel.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_panel.tsx @@ -19,7 +19,10 @@ import { AnalyticsTable } from './table'; import { getAnalyticsFactory } from '../../../data_frame_analytics/pages/analytics_management/services/analytics_service'; import { DataFrameAnalyticsListRow } from '../../../data_frame_analytics/pages/analytics_management/components/analytics_list/common'; -export const AnalyticsPanel: FC = () => { +interface Props { + jobCreationDisabled: boolean; +} +export const AnalyticsPanel: FC = ({ jobCreationDisabled }) => { const [analytics, setAnalytics] = useState([]); const [errorMessage, setErrorMessage] = useState(undefined); const [isInitialized, setIsInitialized] = useState(false); @@ -67,7 +70,7 @@ export const AnalyticsPanel: FC = () => { title={

{i18n.translate('xpack.ml.overview.analyticsList.createFirstJobMessage', { - defaultMessage: 'Create your first analytics job.', + defaultMessage: 'Create your first analytics job', })}

} @@ -81,7 +84,13 @@ export const AnalyticsPanel: FC = () => { } actions={ - + {i18n.translate('xpack.ml.overview.analyticsList.createJobButtonText', { defaultMessage: 'Create job', })} diff --git a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx index 72e17c4d090f4..3c89e72ee4943 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx @@ -50,7 +50,11 @@ function getDefaultAnomalyScores(groups: Group[]): MaxScoresByGroup { return anomalyScores; } -export const AnomalyDetectionPanel: FC = () => { +interface Props { + jobCreationDisabled: boolean; +} + +export const AnomalyDetectionPanel: FC = ({ jobCreationDisabled }) => { const [isLoading, setIsLoading] = useState(false); const [groups, setGroups] = useState({}); const [groupsCount, setGroupsCount] = useState(0); @@ -156,7 +160,7 @@ export const AnomalyDetectionPanel: FC = () => { title={

{i18n.translate('xpack.ml.overview.anomalyDetection.createFirstJobMessage', { - defaultMessage: 'Create your first anomaly detection job.', + defaultMessage: 'Create your first anomaly detection job', })}

} @@ -170,7 +174,13 @@ export const AnomalyDetectionPanel: FC = () => { } actions={ - + {i18n.translate('xpack.ml.overview.anomalyDetection.createJobButtonText', { defaultMessage: 'Create job', })} diff --git a/x-pack/legacy/plugins/ml/public/overview/components/content.tsx b/x-pack/legacy/plugins/ml/public/overview/components/content.tsx index 98295fe0a1a49..a285d5c91a266 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/content.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/components/content.tsx @@ -9,15 +9,23 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { AnomalyDetectionPanel } from './anomaly_detection_panel'; import { AnalyticsPanel } from './analytics_panel/'; +interface Props { + createAnomalyDetectionJobDisabled: boolean; + createAnalyticsJobDisabled: boolean; +} + // Fetch jobs and determine what to show -export const OverviewContent: FC = () => ( +export const OverviewContent: FC = ({ + createAnomalyDetectionJobDisabled, + createAnalyticsJobDisabled, +}) => ( - + - + diff --git a/x-pack/legacy/plugins/ml/public/overview/components/sidebar.tsx b/x-pack/legacy/plugins/ml/public/overview/components/sidebar.tsx index 5a25f9ad54aa1..496beb158f698 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/sidebar.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/components/sidebar.tsx @@ -17,7 +17,27 @@ const feedbackLink = 'https://www.elastic.co/community/'; const transformsLink = `${chrome.getBasePath()}/app/kibana#/management/elasticsearch/transform`; const whatIsMachineLearningLink = 'https://www.elastic.co/what-is/elasticsearch-machine-learning'; -export const OverviewSideBar: FC = () => ( +interface Props { + createAnomalyDetectionJobDisabled: boolean; +} + +function getCreateJobLink(createAnomalyDetectionJobDisabled: boolean) { + return createAnomalyDetectionJobDisabled === true ? ( + + ) : ( + + + + ); +} + +export const OverviewSideBar: FC = ({ createAnomalyDetectionJobDisabled }) => (

@@ -41,14 +61,7 @@ export const OverviewSideBar: FC = () => ( /> ), - createJob: ( - - - - ), + createJob: getCreateJobLink(createAnomalyDetectionJobDisabled), transforms: ( { + const disableCreateAnomalyDetectionJob = !checkPermission('canCreateJob') || !mlNodesAvailable(); + const disableCreateAnalyticsButton = + !checkPermission('canCreateDataFrameAnalytics') || + !checkPermission('canStartStopDataFrameAnalytics'); return ( - - + + diff --git a/x-pack/legacy/plugins/ml/public/overview/route.ts b/x-pack/legacy/plugins/ml/public/overview/route.ts index 4f5ce7e453f9a..c24b537796a00 100644 --- a/x-pack/legacy/plugins/ml/public/overview/route.ts +++ b/x-pack/legacy/plugins/ml/public/overview/route.ts @@ -5,6 +5,7 @@ */ import uiRoutes from 'ui/routes'; +import { getMlNodeCount } from '../ml_nodes_check/check_ml_nodes'; // @ts-ignore no declaration module import { checkFullLicense } from '../license/check_license'; import { checkGetJobsPrivilege } from '../privilege/check_privilege'; @@ -19,5 +20,6 @@ uiRoutes.when('/overview/?', { resolve: { CheckLicense: checkFullLicense, privileges: checkGetJobsPrivilege, + mlNodeCount: getMlNodeCount, }, }); diff --git a/x-pack/legacy/plugins/ml/public/services/__mocks__/cloudwatch_job_caps_response.json b/x-pack/legacy/plugins/ml/public/services/__mocks__/cloudwatch_job_caps_response.json new file mode 100644 index 0000000000000..783b2d4e081bc --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/services/__mocks__/cloudwatch_job_caps_response.json @@ -0,0 +1,905 @@ +{ + "cloudwatch-*": { + "aggs": [ + { + "id": "count", + "title": "Count", + "kibanaName": "count", + "dslName": "count", + "type": "metrics", + "mlModelPlotAgg": { + "max": "max", + "min": "min" + } + }, + { + "id": "high_count", + "title": "High count", + "kibanaName": "count", + "dslName": "count", + "type": "metrics", + "mlModelPlotAgg": { + "max": "max", + "min": "min" + } + }, + { + "id": "low_count", + "title": "Low count", + "kibanaName": "count", + "dslName": "count", + "type": "metrics", + "mlModelPlotAgg": { + "max": "max", + "min": "min" + } + }, + { + "id": "mean", + "title": "Mean", + "kibanaName": "avg", + "dslName": "avg", + "type": "metrics", + "mlModelPlotAgg": { + "max": "avg", + "min": "avg" + }, + "fieldIds": [ + "CPUUtilization", + "DiskReadBytes", + "DiskReadOps", + "DiskWriteBytes", + "DiskWriteOps", + "NetworkIn", + "NetworkOut" + ] + }, + { + "id": "high_mean", + "title": "High mean", + "kibanaName": "avg", + "dslName": "avg", + "type": "metrics", + "mlModelPlotAgg": { + "max": "avg", + "min": "avg" + }, + "fieldIds": [ + "CPUUtilization", + "DiskReadBytes", + "DiskReadOps", + "DiskWriteBytes", + "DiskWriteOps", + "NetworkIn", + "NetworkOut" + ] + }, + { + "id": "low_mean", + "title": "Low mean", + "kibanaName": "avg", + "dslName": "avg", + "type": "metrics", + "mlModelPlotAgg": { + "max": "avg", + "min": "avg" + }, + "fieldIds": [ + "CPUUtilization", + "DiskReadBytes", + "DiskReadOps", + "DiskWriteBytes", + "DiskWriteOps", + "NetworkIn", + "NetworkOut" + ] + }, + { + "id": "sum", + "title": "Sum", + "kibanaName": "sum", + "dslName": "sum", + "type": "metrics", + "mlModelPlotAgg": { + "max": "sum", + "min": "sum" + }, + "fieldIds": [ + "CPUUtilization", + "DiskReadBytes", + "DiskReadOps", + "DiskWriteBytes", + "DiskWriteOps", + "NetworkIn", + "NetworkOut" + ] + }, + { + "id": "high_sum", + "title": "High sum", + "kibanaName": "sum", + "dslName": "sum", + "type": "metrics", + "mlModelPlotAgg": { + "max": "sum", + "min": "sum" + }, + "fieldIds": [ + "CPUUtilization", + "DiskReadBytes", + "DiskReadOps", + "DiskWriteBytes", + "DiskWriteOps", + "NetworkIn", + "NetworkOut" + ] + }, + { + "id": "low_sum", + "title": "Low sum", + "kibanaName": "sum", + "dslName": "sum", + "type": "metrics", + "mlModelPlotAgg": { + "max": "sum", + "min": "sum" + }, + "fieldIds": [ + "CPUUtilization", + "DiskReadBytes", + "DiskReadOps", + "DiskWriteBytes", + "DiskWriteOps", + "NetworkIn", + "NetworkOut" + ] + }, + { + "id": "median", + "title": "Median", + "kibanaName": "median", + "dslName": "percentiles", + "type": "metrics", + "mlModelPlotAgg": { + "max": "max", + "min": "min" + }, + "fieldIds": [ + "CPUUtilization", + "DiskReadBytes", + "DiskReadOps", + "DiskWriteBytes", + "DiskWriteOps", + "NetworkIn", + "NetworkOut" + ] + }, + { + "id": "high_median", + "title": "High median", + "kibanaName": "median", + "dslName": "percentiles", + "type": "metrics", + "mlModelPlotAgg": { + "max": "max", + "min": "min" + }, + "fieldIds": [ + "CPUUtilization", + "DiskReadBytes", + "DiskReadOps", + "DiskWriteBytes", + "DiskWriteOps", + "NetworkIn", + "NetworkOut" + ] + }, + { + "id": "low_median", + "title": "Low median", + "kibanaName": "median", + "dslName": "percentiles", + "type": "metrics", + "mlModelPlotAgg": { + "max": "max", + "min": "min" + }, + "fieldIds": [ + "CPUUtilization", + "DiskReadBytes", + "DiskReadOps", + "DiskWriteBytes", + "DiskWriteOps", + "NetworkIn", + "NetworkOut" + ] + }, + { + "id": "min", + "title": "Min", + "kibanaName": "min", + "dslName": "min", + "type": "metrics", + "mlModelPlotAgg": { + "max": "min", + "min": "min" + }, + "fieldIds": [ + "CPUUtilization", + "DiskReadBytes", + "DiskReadOps", + "DiskWriteBytes", + "DiskWriteOps", + "NetworkIn", + "NetworkOut" + ] + }, + { + "id": "max", + "title": "Max", + "kibanaName": "max", + "dslName": "max", + "type": "metrics", + "mlModelPlotAgg": { + "max": "max", + "min": "max" + }, + "fieldIds": [ + "CPUUtilization", + "DiskReadBytes", + "DiskReadOps", + "DiskWriteBytes", + "DiskWriteOps", + "NetworkIn", + "NetworkOut" + ] + }, + { + "id": "distinct_count", + "title": "Distinct count", + "kibanaName": "cardinality", + "dslName": "cardinality", + "type": "metrics", + "mlModelPlotAgg": { + "max": "max", + "min": "min" + }, + "fieldIds": [ + "instance", + "region", + "sourcetype.keyword", + "CPUUtilization", + "DiskReadBytes", + "DiskReadOps", + "DiskWriteBytes", + "DiskWriteOps", + "NetworkIn", + "NetworkOut" + ] + }, + { + "id": "non_zero_count", + "title": "Non zero count", + "kibanaName": null, + "dslName": null, + "type": "metrics", + "mlModelPlotAgg": { + "max": "max", + "min": "min" + } + }, + { + "id": "high_non_zero_count", + "title": "High non zero count", + "kibanaName": null, + "dslName": null, + "type": "metrics", + "mlModelPlotAgg": { + "max": "max", + "min": "min" + } + }, + { + "id": "low_non_zero_count", + "title": "Low non zero count", + "kibanaName": null, + "dslName": null, + "type": "metrics", + "mlModelPlotAgg": { + "max": "max", + "min": "min" + } + }, + { + "id": "high_distinct_count", + "title": "High distinct count", + "kibanaName": null, + "dslName": null, + "type": "metrics", + "mlModelPlotAgg": { + "max": "max", + "min": "min" + }, + "fieldIds": [ + "instance", + "region", + "sourcetype.keyword", + "CPUUtilization", + "DiskReadBytes", + "DiskReadOps", + "DiskWriteBytes", + "DiskWriteOps", + "NetworkIn", + "NetworkOut" + ] + }, + { + "id": "low_distinct_count", + "title": "Low distinct count", + "kibanaName": null, + "dslName": null, + "type": "metrics", + "mlModelPlotAgg": { + "max": "max", + "min": "min" + }, + "fieldIds": [ + "instance", + "region", + "sourcetype.keyword", + "CPUUtilization", + "DiskReadBytes", + "DiskReadOps", + "DiskWriteBytes", + "DiskWriteOps", + "NetworkIn", + "NetworkOut" + ] + }, + { + "id": "metric", + "title": "Metric", + "kibanaName": null, + "dslName": null, + "type": "metrics", + "mlModelPlotAgg": { + "max": "max", + "min": "min" + }, + "fieldIds": [ + "CPUUtilization", + "DiskReadBytes", + "DiskReadOps", + "DiskWriteBytes", + "DiskWriteOps", + "NetworkIn", + "NetworkOut" + ] + }, + { + "id": "varp", + "title": "varp", + "kibanaName": null, + "dslName": null, + "type": "metrics", + "mlModelPlotAgg": { + "max": "max", + "min": "min" + }, + "fieldIds": [ + "CPUUtilization", + "DiskReadBytes", + "DiskReadOps", + "DiskWriteBytes", + "DiskWriteOps", + "NetworkIn", + "NetworkOut" + ] + }, + { + "id": "high_varp", + "title": "High varp", + "kibanaName": null, + "dslName": null, + "type": "metrics", + "mlModelPlotAgg": { + "max": "max", + "min": "min" + }, + "fieldIds": [ + "CPUUtilization", + "DiskReadBytes", + "DiskReadOps", + "DiskWriteBytes", + "DiskWriteOps", + "NetworkIn", + "NetworkOut" + ] + }, + { + "id": "low_varp", + "title": "Low varp", + "kibanaName": null, + "dslName": null, + "type": "metrics", + "mlModelPlotAgg": { + "max": "max", + "min": "min" + }, + "fieldIds": [ + "CPUUtilization", + "DiskReadBytes", + "DiskReadOps", + "DiskWriteBytes", + "DiskWriteOps", + "NetworkIn", + "NetworkOut" + ] + }, + { + "id": "non_null_sum", + "title": "Non null sum", + "kibanaName": null, + "dslName": null, + "type": "metrics", + "mlModelPlotAgg": { + "max": "max", + "min": "min" + }, + "fieldIds": [ + "CPUUtilization", + "DiskReadBytes", + "DiskReadOps", + "DiskWriteBytes", + "DiskWriteOps", + "NetworkIn", + "NetworkOut" + ] + }, + { + "id": "high_non_null_sum", + "title": "High non null sum", + "kibanaName": null, + "dslName": null, + "type": "metrics", + "mlModelPlotAgg": { + "max": "max", + "min": "min" + }, + "fieldIds": [ + "CPUUtilization", + "DiskReadBytes", + "DiskReadOps", + "DiskWriteBytes", + "DiskWriteOps", + "NetworkIn", + "NetworkOut" + ] + }, + { + "id": "low_non_null_sum", + "title": "Low non null sum", + "kibanaName": null, + "dslName": null, + "type": "metrics", + "mlModelPlotAgg": { + "max": "max", + "min": "min" + }, + "fieldIds": [ + "CPUUtilization", + "DiskReadBytes", + "DiskReadOps", + "DiskWriteBytes", + "DiskWriteOps", + "NetworkIn", + "NetworkOut" + ] + }, + { + "id": "rare", + "title": "Rare", + "kibanaName": null, + "dslName": null, + "type": "metrics", + "mlModelPlotAgg": { + "max": "max", + "min": "min" + } + }, + { + "id": "freq_rare", + "title": "Freq rare", + "kibanaName": null, + "dslName": null, + "type": "metrics", + "mlModelPlotAgg": { + "max": "max", + "min": "min" + } + }, + { + "id": "info_content", + "title": "Info content", + "kibanaName": null, + "dslName": null, + "type": "metrics", + "mlModelPlotAgg": { + "max": "max", + "min": "min" + }, + "fieldIds": [ + "CPUUtilization", + "DiskReadBytes", + "DiskReadOps", + "DiskWriteBytes", + "DiskWriteOps", + "NetworkIn", + "NetworkOut" + ] + }, + { + "id": "high_info_content", + "title": "High info content", + "kibanaName": null, + "dslName": null, + "type": "metrics", + "mlModelPlotAgg": { + "max": "max", + "min": "min" + }, + "fieldIds": [ + "CPUUtilization", + "DiskReadBytes", + "DiskReadOps", + "DiskWriteBytes", + "DiskWriteOps", + "NetworkIn", + "NetworkOut" + ] + }, + { + "id": "low_info_content", + "title": "Low info content", + "kibanaName": null, + "dslName": null, + "type": "metrics", + "mlModelPlotAgg": { + "max": "max", + "min": "min" + }, + "fieldIds": [ + "CPUUtilization", + "DiskReadBytes", + "DiskReadOps", + "DiskWriteBytes", + "DiskWriteOps", + "NetworkIn", + "NetworkOut" + ] + }, + { + "id": "time_of_day", + "title": "Time of day", + "kibanaName": null, + "dslName": null, + "type": "metrics", + "mlModelPlotAgg": { + "max": "max", + "min": "min" + } + }, + { + "id": "time_of_week", + "title": "Time of week", + "kibanaName": null, + "dslName": null, + "type": "metrics", + "mlModelPlotAgg": { + "max": "max", + "min": "min" + } + }, + { + "id": "lat_long", + "title": "Lat long", + "kibanaName": null, + "dslName": null, + "type": "metrics", + "mlModelPlotAgg": { + "max": "max", + "min": "min" + }, + "fieldIds": [ + "CPUUtilization", + "DiskReadBytes", + "DiskReadOps", + "DiskWriteBytes", + "DiskWriteOps", + "NetworkIn", + "NetworkOut" + ] + } + ], + "fields": [ + { + "id": "@timestamp", + "name": "@timestamp", + "type": "date", + "aggregatable": true, + "aggIds": [] + }, + { + "id": "CPUUtilization", + "name": "CPUUtilization", + "type": "double", + "aggregatable": true, + "aggIds": [ + "mean", + "high_mean", + "low_mean", + "sum", + "high_sum", + "low_sum", + "median", + "high_median", + "low_median", + "min", + "max", + "distinct_count", + "high_distinct_count", + "low_distinct_count", + "metric", + "varp", + "high_varp", + "low_varp", + "non_null_sum", + "high_non_null_sum", + "low_non_null_sum", + "info_content", + "high_info_content", + "low_info_content", + "lat_long" + ] + }, + { + "id": "DiskReadBytes", + "name": "DiskReadBytes", + "type": "double", + "aggregatable": true, + "aggIds": [ + "mean", + "high_mean", + "low_mean", + "sum", + "high_sum", + "low_sum", + "median", + "high_median", + "low_median", + "min", + "max", + "distinct_count", + "high_distinct_count", + "low_distinct_count", + "metric", + "varp", + "high_varp", + "low_varp", + "non_null_sum", + "high_non_null_sum", + "low_non_null_sum", + "info_content", + "high_info_content", + "low_info_content", + "lat_long" + ] + }, + { + "id": "DiskReadOps", + "name": "DiskReadOps", + "type": "double", + "aggregatable": true, + "aggIds": [ + "mean", + "high_mean", + "low_mean", + "sum", + "high_sum", + "low_sum", + "median", + "high_median", + "low_median", + "min", + "max", + "distinct_count", + "high_distinct_count", + "low_distinct_count", + "metric", + "varp", + "high_varp", + "low_varp", + "non_null_sum", + "high_non_null_sum", + "low_non_null_sum", + "info_content", + "high_info_content", + "low_info_content", + "lat_long" + ] + }, + { + "id": "DiskWriteBytes", + "name": "DiskWriteBytes", + "type": "double", + "aggregatable": true, + "aggIds": [ + "mean", + "high_mean", + "low_mean", + "sum", + "high_sum", + "low_sum", + "median", + "high_median", + "low_median", + "min", + "max", + "distinct_count", + "high_distinct_count", + "low_distinct_count", + "metric", + "varp", + "high_varp", + "low_varp", + "non_null_sum", + "high_non_null_sum", + "low_non_null_sum", + "info_content", + "high_info_content", + "low_info_content", + "lat_long" + ] + }, + { + "id": "DiskWriteOps", + "name": "DiskWriteOps", + "type": "double", + "aggregatable": true, + "aggIds": [ + "mean", + "high_mean", + "low_mean", + "sum", + "high_sum", + "low_sum", + "median", + "high_median", + "low_median", + "min", + "max", + "distinct_count", + "high_distinct_count", + "low_distinct_count", + "metric", + "varp", + "high_varp", + "low_varp", + "non_null_sum", + "high_non_null_sum", + "low_non_null_sum", + "info_content", + "high_info_content", + "low_info_content", + "lat_long" + ] + }, + { + "id": "instance", + "name": "instance", + "type": "keyword", + "aggregatable": true, + "aggIds": [ + "distinct_count", + "high_distinct_count", + "low_distinct_count" + ] + }, + { + "id": "NetworkIn", + "name": "NetworkIn", + "type": "double", + "aggregatable": true, + "aggIds": [ + "mean", + "high_mean", + "low_mean", + "sum", + "high_sum", + "low_sum", + "median", + "high_median", + "low_median", + "min", + "max", + "distinct_count", + "high_distinct_count", + "low_distinct_count", + "metric", + "varp", + "high_varp", + "low_varp", + "non_null_sum", + "high_non_null_sum", + "low_non_null_sum", + "info_content", + "high_info_content", + "low_info_content", + "lat_long" + ] + }, + { + "id": "NetworkOut", + "name": "NetworkOut", + "type": "double", + "aggregatable": true, + "aggIds": [ + "mean", + "high_mean", + "low_mean", + "sum", + "high_sum", + "low_sum", + "median", + "high_median", + "low_median", + "min", + "max", + "distinct_count", + "high_distinct_count", + "low_distinct_count", + "metric", + "varp", + "high_varp", + "low_varp", + "non_null_sum", + "high_non_null_sum", + "low_non_null_sum", + "info_content", + "high_info_content", + "low_info_content", + "lat_long" + ] + }, + { + "id": "region", + "name": "region", + "type": "keyword", + "aggregatable": true, + "aggIds": [ + "distinct_count", + "high_distinct_count", + "low_distinct_count" + ] + }, + { + "id": "sourcetype", + "name": "sourcetype", + "type": "text", + "aggregatable": false, + "aggIds": [] + }, + { + "id": "sourcetype.keyword", + "name": "sourcetype.keyword", + "type": "keyword", + "aggregatable": true, + "aggIds": [ + "distinct_count", + "high_distinct_count", + "low_distinct_count" + ] + } + ] + } +} diff --git a/x-pack/legacy/plugins/ml/public/services/__mocks__/farequote_job_caps_response.json b/x-pack/legacy/plugins/ml/public/services/__mocks__/farequote_job_caps_response.json deleted file mode 100644 index cff91d90e8ffc..0000000000000 --- a/x-pack/legacy/plugins/ml/public/services/__mocks__/farequote_job_caps_response.json +++ /dev/null @@ -1,528 +0,0 @@ -{ - "farequote-*": { - "aggs": [ - { - "id": "count", - "title": "Count", - "kibanaName": "count", - "dslName": "count", - "type": "metrics", - "mlModelPlotAgg": { - "max": "max", - "min": "min" - }, - "fieldIds": [] - }, - { - "id": "high_count", - "title": "High count", - "kibanaName": "count", - "dslName": "count", - "type": "metrics", - "mlModelPlotAgg": { - "max": "max", - "min": "min" - }, - "fieldIds": [] - }, - { - "id": "low_count", - "title": "Low count", - "kibanaName": "count", - "dslName": "count", - "type": "metrics", - "mlModelPlotAgg": { - "max": "max", - "min": "min" - }, - "fieldIds": [] - }, - { - "id": "mean", - "title": "Mean", - "kibanaName": "avg", - "dslName": "avg", - "type": "metrics", - "mlModelPlotAgg": { - "max": "avg", - "min": "avg" - }, - "fieldIds": [ - "responsetime" - ] - }, - { - "id": "high_mean", - "title": "High mean", - "kibanaName": "avg", - "dslName": "avg", - "type": "metrics", - "mlModelPlotAgg": { - "max": "avg", - "min": "avg" - }, - "fieldIds": [ - "responsetime" - ] - }, - { - "id": "low_mean", - "title": "Low mean", - "kibanaName": "avg", - "dslName": "avg", - "type": "metrics", - "mlModelPlotAgg": { - "max": "avg", - "min": "avg" - }, - "fieldIds": [ - "responsetime" - ] - }, - { - "id": "sum", - "title": "Sum", - "kibanaName": "sum", - "dslName": "sum", - "type": "metrics", - "mlModelPlotAgg": { - "max": "sum", - "min": "sum" - }, - "fieldIds": [ - "responsetime" - ] - }, - { - "id": "high_sum", - "title": "High sum", - "kibanaName": "sum", - "dslName": "sum", - "type": "metrics", - "mlModelPlotAgg": { - "max": "sum", - "min": "sum" - }, - "fieldIds": [ - "responsetime" - ] - }, - { - "id": "low_sum", - "title": "Low sum", - "kibanaName": "sum", - "dslName": "sum", - "type": "metrics", - "mlModelPlotAgg": { - "max": "sum", - "min": "sum" - }, - "fieldIds": [ - "responsetime" - ] - }, - { - "id": "median", - "title": "Median", - "kibanaName": "median", - "dslName": "percentiles", - "type": "metrics", - "mlModelPlotAgg": { - "max": "max", - "min": "min" - }, - "fieldIds": [ - "responsetime" - ] - }, - { - "id": "high_median", - "title": "High median", - "kibanaName": "median", - "dslName": "percentiles", - "type": "metrics", - "mlModelPlotAgg": { - "max": "max", - "min": "min" - }, - "fieldIds": [ - "responsetime" - ] - }, - { - "id": "low_median", - "title": "Low median", - "kibanaName": "median", - "dslName": "percentiles", - "type": "metrics", - "mlModelPlotAgg": { - "max": "max", - "min": "min" - }, - "fieldIds": [ - "responsetime" - ] - }, - { - "id": "min", - "title": "Min", - "kibanaName": "min", - "dslName": "min", - "type": "metrics", - "mlModelPlotAgg": { - "max": "min", - "min": "min" - }, - "fieldIds": [ - "responsetime" - ] - }, - { - "id": "max", - "title": "Max", - "kibanaName": "max", - "dslName": "max", - "type": "metrics", - "mlModelPlotAgg": { - "max": "max", - "min": "max" - }, - "fieldIds": [ - "responsetime" - ] - }, - { - "id": "distinct_count", - "title": "Distinct count", - "kibanaName": "cardinality", - "dslName": "cardinality", - "type": "metrics", - "mlModelPlotAgg": { - "max": "max", - "min": "min" - }, - "fieldIds": [ - "airline", - "responsetime" - ] - }, - { - "id": "non_zero_count", - "title": "Non zero count", - "kibanaName": null, - "dslName": null, - "type": "metrics", - "mlModelPlotAgg": { - "max": "max", - "min": "min" - }, - "fieldIds": [] - }, - { - "id": "high_non_zero_count", - "title": "High non zero count", - "kibanaName": null, - "dslName": null, - "type": "metrics", - "mlModelPlotAgg": { - "max": "max", - "min": "min" - }, - "fieldIds": [] - }, - { - "id": "low_non_zero_count", - "title": "Low non zero count", - "kibanaName": null, - "dslName": null, - "type": "metrics", - "mlModelPlotAgg": { - "max": "max", - "min": "min" - }, - "fieldIds": [] - }, - { - "id": "high_distinct_count", - "title": "High distinct count", - "kibanaName": null, - "dslName": null, - "type": "metrics", - "mlModelPlotAgg": { - "max": "max", - "min": "min" - }, - "fieldIds": [ - "responsetime" - ] - }, - { - "id": "low_distinct_count", - "title": "Low distinct count", - "kibanaName": null, - "dslName": null, - "type": "metrics", - "mlModelPlotAgg": { - "max": "max", - "min": "min" - }, - "fieldIds": [ - "responsetime" - ] - }, - { - "id": "metric", - "title": "Metric", - "kibanaName": null, - "dslName": null, - "type": "metrics", - "mlModelPlotAgg": { - "max": "max", - "min": "min" - }, - "fieldIds": [ - "responsetime" - ] - }, - { - "id": "varp", - "title": "varp", - "kibanaName": null, - "dslName": null, - "type": "metrics", - "mlModelPlotAgg": { - "max": "max", - "min": "min" - }, - "fieldIds": [ - "responsetime" - ] - }, - { - "id": "high_varp", - "title": "High varp", - "kibanaName": null, - "dslName": null, - "type": "metrics", - "mlModelPlotAgg": { - "max": "max", - "min": "min" - }, - "fieldIds": [ - "responsetime" - ] - }, - { - "id": "low_varp", - "title": "Low varp", - "kibanaName": null, - "dslName": null, - "type": "metrics", - "mlModelPlotAgg": { - "max": "max", - "min": "min" - }, - "fieldIds": [ - "responsetime" - ] - }, - { - "id": "non_null_sum", - "title": "Non null sum", - "kibanaName": null, - "dslName": null, - "type": "metrics", - "mlModelPlotAgg": { - "max": "max", - "min": "min" - }, - "fieldIds": [ - "responsetime" - ] - }, - { - "id": "high_non_null_sum", - "title": "High non null sum", - "kibanaName": null, - "dslName": null, - "type": "metrics", - "mlModelPlotAgg": { - "max": "max", - "min": "min" - }, - "fieldIds": [ - "responsetime" - ] - }, - { - "id": "low_non_null_sum", - "title": "Low non null sum", - "kibanaName": null, - "dslName": null, - "type": "metrics", - "mlModelPlotAgg": { - "max": "max", - "min": "min" - }, - "fieldIds": [ - "responsetime" - ] - }, - { - "id": "rare", - "title": "Rare", - "kibanaName": null, - "dslName": null, - "type": "metrics", - "mlModelPlotAgg": { - "max": "max", - "min": "min" - }, - "fieldIds": [] - }, - { - "id": "freq_rare", - "title": "Freq rare", - "kibanaName": null, - "dslName": null, - "type": "metrics", - "mlModelPlotAgg": { - "max": "max", - "min": "min" - }, - "fieldIds": [] - }, - { - "id": "info_content", - "title": "Info content", - "kibanaName": null, - "dslName": null, - "type": "metrics", - "mlModelPlotAgg": { - "max": "max", - "min": "min" - }, - "fieldIds": [ - "responsetime" - ] - }, - { - "id": "high_info_content", - "title": "High info content", - "kibanaName": null, - "dslName": null, - "type": "metrics", - "mlModelPlotAgg": { - "max": "max", - "min": "min" - }, - "fieldIds": [ - "responsetime" - ] - }, - { - "id": "low_info_content", - "title": "Low info content", - "kibanaName": null, - "dslName": null, - "type": "metrics", - "mlModelPlotAgg": { - "max": "max", - "min": "min" - }, - "fieldIds": [ - "responsetime" - ] - }, - { - "id": "time_of_day", - "title": "Time of day", - "kibanaName": null, - "dslName": null, - "type": "metrics", - "mlModelPlotAgg": { - "max": "max", - "min": "min" - }, - "fieldIds": [] - }, - { - "id": "time_of_week", - "title": "Time of week", - "kibanaName": null, - "dslName": null, - "type": "metrics", - "mlModelPlotAgg": { - "max": "max", - "min": "min" - }, - "fieldIds": [] - }, - { - "id": "lat_long", - "title": "Lat long", - "kibanaName": null, - "dslName": null, - "type": "metrics", - "mlModelPlotAgg": { - "max": "max", - "min": "min" - }, - "fieldIds": [ - "responsetime" - ] - } - ], - "fields": [ - { - "id": "@timestamp", - "name": "@timestamp", - "type": "date", - "aggregatable": true, - "aggIds": [] - }, - { - "id": "airline", - "name": "airline", - "type": "keyword", - "aggregatable": true, - "aggIds": [ - "distinct_count" - ] - }, - { - "id": "responsetime", - "name": "responsetime", - "type": "float", - "aggregatable": true, - "aggIds": [ - "mean", - "high_mean", - "low_mean", - "sum", - "high_sum", - "low_sum", - "median", - "high_median", - "low_median", - "min", - "max", - "distinct_count", - "high_distinct_count", - "low_distinct_count", - "metric", - "varp", - "high_varp", - "low_varp", - "non_null_sum", - "high_non_null_sum", - "low_non_null_sum", - "info_content", - "high_info_content", - "low_info_content", - "lat_long" - ] - } - ] - } -} diff --git a/x-pack/legacy/plugins/ml/public/services/new_job_capabilities._service.test.ts b/x-pack/legacy/plugins/ml/public/services/new_job_capabilities._service.test.ts index fc8bb2407f75a..91c945ea8ff29 100644 --- a/x-pack/legacy/plugins/ml/public/services/new_job_capabilities._service.test.ts +++ b/x-pack/legacy/plugins/ml/public/services/new_job_capabilities._service.test.ts @@ -9,40 +9,60 @@ import { IndexPattern } from 'ui/index_patterns'; // there is magic happening here. starting the include name with `mock..` // ensures it can be lazily loaded by the jest.mock function below. -import mockFarequoteResponse from './__mocks__/farequote_job_caps_response.json'; +import mockCloudwatchResponse from './__mocks__/cloudwatch_job_caps_response.json'; jest.mock('./ml_api_service', () => ({ ml: { jobs: { - newJobCaps: jest.fn(() => Promise.resolve(mockFarequoteResponse)), + newJobCaps: jest.fn(() => Promise.resolve(mockCloudwatchResponse)), }, }, })); const indexPattern = ({ - id: 'farequote-*', - title: 'farequote-*', + id: 'cloudwatch-*', + title: 'cloudwatch-*', } as unknown) as IndexPattern; describe('new_job_capabilities_service', () => { - describe('farequote newJobCaps()', () => { + describe('cloudwatch newJobCaps()', () => { it('can construct job caps objects from endpoint json', async done => { await newJobCapsService.initializeFromIndexPattern(indexPattern); const { fields, aggs } = await newJobCapsService.newJobCaps; - const responseTimeField = fields.find(f => f.id === 'responsetime') || { aggs: [] }; - const airlineField = fields.find(f => f.id === 'airline') || { aggs: [] }; + 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(4); + expect(fields).toHaveLength(12); expect(aggs).toHaveLength(35); - expect(responseTimeField.aggs).toHaveLength(25); - expect(airlineField.aggs).toHaveLength(1); + expect(networkOutField.aggs).toHaveLength(25); + expect(regionField.aggs).toHaveLength(3); + + expect(meanAgg.fields).toHaveLength(7); + expect(distinctCountAgg.fields).toHaveLength(10); + done(); + }); + + it('job caps including text fields', async done => { + await newJobCapsService.initializeFromIndexPattern(indexPattern, true, false); + const { fields, aggs } = await newJobCapsService.newJobCaps; + + expect(fields).toHaveLength(13); // one more field + expect(aggs).toHaveLength(35); + + done(); + }); + + it('job caps excluding event rate', async done => { + await newJobCapsService.initializeFromIndexPattern(indexPattern, false, true); + const { fields, aggs } = await newJobCapsService.newJobCaps; + + expect(fields).toHaveLength(11); // one less field + expect(aggs).toHaveLength(35); - expect(meanAgg.fields).toHaveLength(1); - expect(distinctCountAgg.fields).toHaveLength(2); done(); }); }); diff --git a/x-pack/legacy/plugins/ml/public/services/new_job_capabilities_service.ts b/x-pack/legacy/plugins/ml/public/services/new_job_capabilities_service.ts index 890683793339a..ded9aa410766e 100644 --- a/x-pack/legacy/plugins/ml/public/services/new_job_capabilities_service.ts +++ b/x-pack/legacy/plugins/ml/public/services/new_job_capabilities_service.ts @@ -50,20 +50,25 @@ export function loadNewJobCapabilities( const categoryFieldTypes = [ES_FIELD_TYPES.TEXT, ES_FIELD_TYPES.KEYWORD, ES_FIELD_TYPES.IP]; class NewJobCapsService { - private _fields: Field[]; - private _aggs: Aggregation[]; - private _includeEventRateField: boolean; - - constructor(includeEventRateField = true) { - this._fields = []; - this._aggs = []; - this._includeEventRateField = includeEventRateField; - } + private _fields: Field[] = []; + private _catFields: Field[] = []; + private _dateFields: Field[] = []; + private _aggs: Aggregation[] = []; + private _includeEventRateField: boolean = true; + private _removeTextFields: boolean = true; public get fields(): Field[] { return this._fields; } + public get catFields(): Field[] { + return this._catFields; + } + + public get dateFields(): Field[] { + return this._dateFields; + } + public get aggs(): Aggregation[] { return this._aggs; } @@ -79,16 +84,38 @@ class NewJobCapsService { return this._fields.filter(f => categoryFieldTypes.includes(f.type)); } - public async initializeFromIndexPattern(indexPattern: IndexPattern) { + public async initializeFromIndexPattern( + indexPattern: IndexPattern, + includeEventRateField = true, + removeTextFields = true + ) { try { + this._includeEventRateField = includeEventRateField; + this._removeTextFields = removeTextFields; + const resp = await ml.jobs.newJobCaps(indexPattern.title, indexPattern.type === 'rollup'); - const { fields, aggs } = createObjects(resp, indexPattern.title); + const { fields: allFields, aggs } = createObjects(resp, indexPattern.title); if (this._includeEventRateField === true) { - addEventRateField(aggs, fields); + addEventRateField(aggs, allFields); } + const { fieldsPreferringKeyword, fieldsPreferringText } = processTextAndKeywordFields( + allFields + ); + const catFields = fieldsPreferringText.filter( + 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 fields = this._removeTextFields ? fieldsPreferringKeyword : allFields; + + // set the main fields list to contain fields which have been filtered to prefer + // keyword fields over text fields. + // e.g. if foo.keyword and foo exist, don't add foo to the list. this._fields = fields; + // set the category fields to contain fields which have been filtered to prefer text fields. + this._catFields = catFields; + this._dateFields = dateFields; this._aggs = aggs; } catch (error) { console.error('Unable to load new job capabilities', error); // eslint-disable-line no-console @@ -199,4 +226,25 @@ function addEventRateField(aggs: Aggregation[], fields: Field[]) { fields.splice(0, 0, eventRateField); } +// 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 fieldsPreferringKeyword = fields.filter( + 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.type !== ES_FIELD_TYPES.KEYWORD || + (f.type === ES_FIELD_TYPES.KEYWORD && + textIds.includes(f.id.replace(/\.keyword$/, '')) === false) + ); + + return { fieldsPreferringKeyword, fieldsPreferringText }; +} + export const newJobCapsService = new NewJobCapsService(); diff --git a/x-pack/legacy/plugins/ml/public/timeseriesexplorer/timeseriesexplorer.js b/x-pack/legacy/plugins/ml/public/timeseriesexplorer/timeseriesexplorer.js index 802772e55078c..73d1dd2818042 100644 --- a/x-pack/legacy/plugins/ml/public/timeseriesexplorer/timeseriesexplorer.js +++ b/x-pack/legacy/plugins/ml/public/timeseriesexplorer/timeseriesexplorer.js @@ -1173,12 +1173,13 @@ export class TimeSeriesExplorer extends React.Component { /> )} - {(arePartitioningFieldsProvided && jobs.length > 0 && loading === false && hasResults === false) && ( + {(arePartitioningFieldsProvided && jobs.length > 0 && (fullRefresh === false || loading === false) && hasResults === false) && ( )} - {(arePartitioningFieldsProvided && jobs.length > 0 && loading === false && hasResults === true) && ( + {(arePartitioningFieldsProvided && jobs.length > 0 && (fullRefresh === false || loading === false) && hasResults === true) && ( + {/* Make sure ChartTooltip is inside this plain wrapping element so positioning can be infered correctly. */} diff --git a/x-pack/legacy/plugins/ml/public/util/custom_url_utils.test.ts b/x-pack/legacy/plugins/ml/public/util/custom_url_utils.test.ts index 6eb33d92a85f7..d4556f085f4b2 100644 --- a/x-pack/legacy/plugins/ml/public/util/custom_url_utils.test.ts +++ b/x-pack/legacy/plugins/ml/public/util/custom_url_utils.test.ts @@ -269,6 +269,43 @@ describe('ML - custom URL utils', () => { ); // eslint-disable-line max-len }); + test('replaces tokens with nesting', () => { + const testUrlApache: KibanaUrlConfig = { + url_name: 'Raw data', + time_range: 'auto', + url_value: + 'kibana#/dashboard/ml_http_access_explorer_ecs?_g=(time:(from:\u0027$earliest$\u0027,mode:absolute,to:\u0027$latest$\u0027))&_a=(description:\u0027\u0027,filters:!((\u0027$state\u0027:(store:appState),meta:(alias:!n,disabled:!f,index:\u0027INDEX_PATTERN_ID\u0027,key:event.dataset,negate:!f,params:(query:\u0027apache.access\u0027),type:phrase,value:\u0027apache.access\u0027),query:(match:(event.dataset:(query:\u0027apache.access\u0027,type:phrase)))),(\u0027$state\u0027:(store:appState),meta:(alias:!n,disabled:!f,index:\u0027INDEX_PATTERN_ID\u0027,key:http.response.status_code,negate:!f,params:(query:\u0027$http.response.status_code$\u0027),type:phrase,value:\u0027$http.response.status_code$\u0027),query:(match:(http.response.status_code:(query:\u0027$http.response.status_code$\u0027,type:phrase))))),query:(language:kuery,query:\u0027\u0027))', // eslint-disable-line max-len + }; + + const testRecord = { + job_id: 'farequote', + result_type: 'record', + probability: 6.533287347648861e-45, + record_score: 93.84475, + initial_record_score: 94.867922946384, + bucket_span: 300, + detector_index: 0, + is_interim: false, + timestamp: 1486656600000, + function: 'mean', + function_description: 'mean', + typical: [99.2329899996025], + actual: [274.7279901504516], + field_name: 'responsetime', + earliest: '2017-02-09T15:10:00.000Z', + latest: '2017-02-09T17:15:00.000Z', + http: { + response: { + status_code: 403, + }, + }, + }; + + expect(getUrlForRecord(testUrlApache, testRecord)).toBe( + "kibana#/dashboard/ml_http_access_explorer_ecs?_g=(time:(from:'2017-02-09T15:10:00.000Z',mode:absolute,to:'2017-02-09T17:15:00.000Z'))&_a=(description:\u0027\u0027,filters:!((\u0027$state\u0027:(store:appState),meta:(alias:!n,disabled:!f,index:\u0027INDEX_PATTERN_ID\u0027,key:event.dataset,negate:!f,params:(query:\u0027apache.access\u0027),type:phrase,value:\u0027apache.access\u0027),query:(match:(event.dataset:(query:\u0027apache.access\u0027,type:phrase)))),(\u0027$state\u0027:(store:appState),meta:(alias:!n,disabled:!f,index:\u0027INDEX_PATTERN_ID\u0027,key:http.response.status_code,negate:!f,params:(query:\u0027403\u0027),type:phrase,value:\u0027403\u0027),query:(match:(http.response.status_code:(query:\u0027403\u0027,type:phrase))))),query:(language:kuery,query:\u0027\u0027))" + ); // eslint-disable-line max-len + }); + test('returns expected URL for other type URL', () => { expect(getUrlForRecord(TEST_OTHER_URL, TEST_RECORD)).toBe( 'http://airlinecodes.info/airline-code-AAL' diff --git a/x-pack/legacy/plugins/ml/public/util/custom_url_utils.ts b/x-pack/legacy/plugins/ml/public/util/custom_url_utils.ts index 61c0f3a159f14..7fda7a88d318f 100644 --- a/x-pack/legacy/plugins/ml/public/util/custom_url_utils.ts +++ b/x-pack/legacy/plugins/ml/public/util/custom_url_utils.ts @@ -103,8 +103,8 @@ function isKibanaUrl(urlConfig: UrlConfig) { /** * Escape any double quotes in the value for correct use in KQL. */ -function escapeForKQL(value: string): string { - return value.replace(/\"/g, '\\"'); +function escapeForKQL(value: string | number): string { + return String(value).replace(/\"/g, '\\"'); } // Builds a Kibana dashboard or Discover URL from the supplied config, with any @@ -127,7 +127,7 @@ function buildKibanaUrl(urlConfig: UrlConfig, record: CustomUrlAnomalyRecordDoc) ); const replaceSingleTokenValues = (str: string) => - str.replace(/\$(\w+)\$/g, (match, name: string) => { + str.replace(/\$([^?&$\'"]+)\$/g, (match, name: string) => { // Use lodash get to allow nested JSON fields to be retrieved. let tokenValue: string | string[] | undefined = get(record, name); tokenValue = Array.isArray(tokenValue) ? tokenValue[0] : tokenValue; diff --git a/x-pack/legacy/plugins/ml/public/util/string_utils.js b/x-pack/legacy/plugins/ml/public/util/string_utils.js index 7d97f0b1d3167..cb1ccafc5e605 100644 --- a/x-pack/legacy/plugins/ml/public/util/string_utils.js +++ b/x-pack/legacy/plugins/ml/public/util/string_utils.js @@ -20,7 +20,7 @@ import d3 from 'd3'; // 'http://www.google.co.uk/#q=airline+code+AAL'. // If a corresponding key is not found in valuesByTokenName, then the String is not replaced. export function replaceStringTokens(str, valuesByTokenName, encodeForURI) { - return String(str).replace((/\$([^?&$\'"]{1,40})\$/g), (match, name) => { + return String(str).replace((/\$([^?&$\'"]+)\$/g), (match, name) => { // Use lodash get to allow nested JSON fields to be retrieved. let tokenValue = _.get(valuesByTokenName, name, null); if (encodeForURI === true && tokenValue !== null) { diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/logo.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/logo.json index ead765e474720..26b404ff331c6 100644 --- a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/logo.json +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/logo.json @@ -1,3 +1,3 @@ { - "icon": "loggingApp" + "icon": "logsApp" } diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/field_service.ts b/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/field_service.ts index df4a2c059f1ef..d7c26bda77d20 100644 --- a/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/field_service.ts +++ b/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/field_service.ts @@ -136,7 +136,8 @@ async function combineFieldsAndAggs( const numericalFields = getNumericalFields(fields); const ipFields = getIpFields(fields); - const mix = mixFactory(rollupFields); + const isRollup = Object.keys(rollupFields).length > 0; + const mix = mixFactory(isRollup, rollupFields); aggs.forEach(a => { if (a.type === METRIC_AGG_TYPE && a.fields !== undefined) { @@ -165,7 +166,7 @@ async function combineFieldsAndAggs( return { aggs, - fields: filterFields(fields), + fields: isRollup ? filterFields(fields) : fields, }; } @@ -178,9 +179,7 @@ function filterFields(fields: Field[]): Field[] { // returns a mix function that is used to cross-reference aggs and fields. // wrapped in a provider to allow filtering based on rollup job capabilities -function mixFactory(rollupFields: RollupFields) { - const isRollup = Object.keys(rollupFields).length > 0; - +function mixFactory(isRollup: boolean, rollupFields: RollupFields) { return function mix(field: Field, agg: Aggregation): void { if ( isRollup === false || diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/new_job_caps.test.ts b/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/new_job_caps.test.ts index 87653a1e3f47d..2c8f8a8f82fb8 100644 --- a/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/new_job_caps.test.ts +++ b/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/new_job_caps.test.ts @@ -66,7 +66,9 @@ describe('job_service - job_caps', () => { expect(response).toEqual(farequoteJobCapsEmpty); done(); }); + }); + describe('cloudwatch newJobCaps()', () => { it('can get rollup job caps for rollup index pattern', async done => { const indexPattern = 'cloud_roll_index'; const isRollup = true; diff --git a/x-pack/legacy/plugins/monitoring/common/__tests__/format_timestamp_to_duration.js b/x-pack/legacy/plugins/monitoring/common/__tests__/format_timestamp_to_duration.js index 6a44693418091..917c9b4a20b13 100644 --- a/x-pack/legacy/plugins/monitoring/common/__tests__/format_timestamp_to_duration.js +++ b/x-pack/legacy/plugins/monitoring/common/__tests__/format_timestamp_to_duration.js @@ -23,22 +23,22 @@ describe('formatTimestampToDuration', () => { expect(formatTimestampToDuration(fiftyNineSeconds, CALCULATE_DURATION_SINCE, getTestTime())).to.be('59 seconds'); const fiveMins = getTestTime().subtract(5, 'minutes').subtract(30, 'seconds'); - expect(formatTimestampToDuration(fiveMins, CALCULATE_DURATION_SINCE, getTestTime())).to.be('5 min'); + expect(formatTimestampToDuration(fiveMins, CALCULATE_DURATION_SINCE, getTestTime())).to.be('6 mins'); const sixHours = getTestTime().subtract(6, 'hours').subtract(30, 'minutes'); - expect(formatTimestampToDuration(sixHours, CALCULATE_DURATION_SINCE, getTestTime())).to.be('6 hrs 30 min'); + expect(formatTimestampToDuration(sixHours, CALCULATE_DURATION_SINCE, getTestTime())).to.be('6 hrs 30 mins'); const sevenDays = getTestTime().subtract(7, 'days').subtract(6, 'hours').subtract(18, 'minutes'); - expect(formatTimestampToDuration(sevenDays, CALCULATE_DURATION_SINCE, getTestTime())).to.be('7 days 6 hrs 18 min'); + expect(formatTimestampToDuration(sevenDays, CALCULATE_DURATION_SINCE, getTestTime())).to.be('7 days 6 hrs 18 mins'); const eightWeeks = getTestTime().subtract(8, 'weeks').subtract(7, 'days').subtract(6, 'hours').subtract(18, 'minutes'); expect(formatTimestampToDuration(eightWeeks, CALCULATE_DURATION_SINCE, getTestTime())).to.be('2 months 2 days'); const oneHour = getTestTime().subtract(1, 'hour'); // should trim 0 min - expect(formatTimestampToDuration(oneHour, CALCULATE_DURATION_SINCE, getTestTime())).to.be('1 hrs'); + expect(formatTimestampToDuration(oneHour, CALCULATE_DURATION_SINCE, getTestTime())).to.be('1 hr'); const oneDay = getTestTime().subtract(1, 'day'); // should trim 0 hrs - expect(formatTimestampToDuration(oneDay, CALCULATE_DURATION_SINCE, getTestTime())).to.be('1 days'); + expect(formatTimestampToDuration(oneDay, CALCULATE_DURATION_SINCE, getTestTime())).to.be('1 day'); const twoMonths = getTestTime().subtract(2, 'month'); // should trim 0 days expect(formatTimestampToDuration(twoMonths, CALCULATE_DURATION_SINCE, getTestTime())).to.be('2 months'); @@ -52,22 +52,22 @@ describe('formatTimestampToDuration', () => { expect(formatTimestampToDuration(fiftyNineSeconds, CALCULATE_DURATION_UNTIL, getTestTime())).to.be('59 seconds'); const fiveMins = getTestTime().add(10, 'minutes'); - expect(formatTimestampToDuration(fiveMins, CALCULATE_DURATION_UNTIL, getTestTime())).to.be('10 min'); + expect(formatTimestampToDuration(fiveMins, CALCULATE_DURATION_UNTIL, getTestTime())).to.be('10 mins'); const sixHours = getTestTime().add(6, 'hours').add(30, 'minutes'); - expect(formatTimestampToDuration(sixHours, CALCULATE_DURATION_UNTIL, getTestTime())).to.be('6 hrs 30 min'); + 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'); - expect(formatTimestampToDuration(sevenDays, CALCULATE_DURATION_UNTIL, getTestTime())).to.be('7 days 6 hrs 18 min'); + expect(formatTimestampToDuration(sevenDays, CALCULATE_DURATION_UNTIL, getTestTime())).to.be('7 days 6 hrs 18 mins'); const eightWeeks = getTestTime().add(8, 'weeks').add(7, 'days').add(6, 'hours').add(18, 'minutes'); expect(formatTimestampToDuration(eightWeeks, CALCULATE_DURATION_UNTIL, getTestTime())).to.be('2 months 2 days'); const oneHour = getTestTime().add(1, 'hour'); // should trim 0 min - expect(formatTimestampToDuration(oneHour, CALCULATE_DURATION_UNTIL, getTestTime())).to.be('1 hrs'); + expect(formatTimestampToDuration(oneHour, CALCULATE_DURATION_UNTIL, getTestTime())).to.be('1 hr'); const oneDay = getTestTime().add(1, 'day'); // should trim 0 hrs - expect(formatTimestampToDuration(oneDay, CALCULATE_DURATION_UNTIL, getTestTime())).to.be('1 days'); + expect(formatTimestampToDuration(oneDay, CALCULATE_DURATION_UNTIL, getTestTime())).to.be('1 day'); const twoMonths = getTestTime().add(2, 'month'); // should trim 0 days expect(formatTimestampToDuration(twoMonths, CALCULATE_DURATION_UNTIL, getTestTime())).to.be('2 months'); diff --git a/x-pack/legacy/plugins/monitoring/common/format_timestamp_to_duration.js b/x-pack/legacy/plugins/monitoring/common/format_timestamp_to_duration.js index 4cf5618bc93db..939931ac8eb37 100644 --- a/x-pack/legacy/plugins/monitoring/common/format_timestamp_to_duration.js +++ b/x-pack/legacy/plugins/monitoring/common/format_timestamp_to_duration.js @@ -48,7 +48,7 @@ export function formatTimestampToDuration(timestamp, calculationFlag, initialTim } return duration - .replace(/ 0 min$/, '') + .replace(/ 0 mins$/, '') .replace(/ 0 hrs$/, '') .replace(/ 0 days$/, ''); // See https://github.com/jsmreese/moment-duration-format/issues/64 } diff --git a/x-pack/legacy/plugins/monitoring/public/components/logs/__snapshots__/logs.test.js.snap b/x-pack/legacy/plugins/monitoring/public/components/logs/__snapshots__/logs.test.js.snap index b108746a422bb..4affadd127530 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/logs/__snapshots__/logs.test.js.snap +++ b/x-pack/legacy/plugins/monitoring/public/components/logs/__snapshots__/logs.test.js.snap @@ -2,7 +2,7 @@ exports[`Logs should render a link to filter by cluster uuid 1`] = ` @@ -26,7 +26,7 @@ exports[`Logs should render a link to filter by cluster uuid 1`] = ` exports[`Logs should render a link to filter by cluster uuid and index uuid 1`] = ` @@ -50,7 +50,7 @@ exports[`Logs should render a link to filter by cluster uuid and index uuid 1`] exports[`Logs should render a link to filter by cluster uuid and node uuid 1`] = ` @@ -275,7 +275,7 @@ exports[`Logs should render normally 1`] = ` size="m" /> diff --git a/x-pack/legacy/plugins/monitoring/public/components/logs/logs.js b/x-pack/legacy/plugins/monitoring/public/components/logs/logs.js index c59a3d595b14f..50e3cbd9af6c9 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/logs/logs.js +++ b/x-pack/legacy/plugins/monitoring/public/components/logs/logs.js @@ -169,7 +169,7 @@ export class Logs extends PureComponent { title={i18n.translate('xpack.monitoring.logs.listing.calloutTitle', { defaultMessage: 'Want to see more log entries?' })} - iconType="loggingApp" + iconType="logsApp" >

{ return new kibana.Plugin({ id: PLUGIN_ID, - require: ['elasticsearch', 'xpack_main', 'task_manager'], + require: ['elasticsearch', 'xpack_main'], configPrefix: 'xpack.oss_telemetry', init(server) { diff --git a/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/visualizations/get_usage_collector.ts b/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/visualizations/get_usage_collector.ts index 2bbecf99b97c3..63640c87f80a6 100644 --- a/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/visualizations/get_usage_collector.ts +++ b/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/visualizations/get_usage_collector.ts @@ -14,7 +14,11 @@ async function isTaskManagerReady(server: HapiServer) { } async function fetch(server: HapiServer) { - const taskManager = server.plugins.task_manager!; + const taskManager = server.plugins.task_manager; + + if (!taskManager) { + return null; + } let docs; try { diff --git a/x-pack/legacy/plugins/oss_telemetry/server/lib/tasks/index.ts b/x-pack/legacy/plugins/oss_telemetry/server/lib/tasks/index.ts index de3a17a79afb6..eaa8cc7405821 100644 --- a/x-pack/legacy/plugins/oss_telemetry/server/lib/tasks/index.ts +++ b/x-pack/legacy/plugins/oss_telemetry/server/lib/tasks/index.ts @@ -11,6 +11,11 @@ import { visualizationsTaskRunner } from './visualizations/task_runner'; export function registerTasks(server: HapiServer) { const taskManager = server.plugins.task_manager; + if (!taskManager) { + server.log(['debug', 'telemetry'], `Task manager is not available`); + return; + } + taskManager.registerTaskDefinitions({ [VIS_TELEMETRY_TASK]: { title: 'X-Pack telemetry calculator for Visualizations', @@ -43,7 +48,7 @@ export function scheduleTasks(server: HapiServer) { state: { stats: {}, runs: 0 }, }); } catch (e) { - server.log(['warning', 'telemetry'], `Error scheduling task, received ${e.message}`); + server.log(['debug', 'telemetry'], `Error scheduling task, received ${e.message}`); } })(); }); diff --git a/x-pack/legacy/plugins/reporting/common/cancellation_token.ts b/x-pack/legacy/plugins/reporting/common/cancellation_token.ts index 11425c3002d97..c03f9ee7328cb 100644 --- a/x-pack/legacy/plugins/reporting/common/cancellation_token.ts +++ b/x-pack/legacy/plugins/reporting/common/cancellation_token.ts @@ -7,11 +7,11 @@ import { isFunction } from 'lodash'; export class CancellationToken { - private isCancelled: boolean; + private _isCancelled: boolean; private _callbacks: Function[]; constructor() { - this.isCancelled = false; + this._isCancelled = false; this._callbacks = []; } @@ -20,7 +20,7 @@ export class CancellationToken { throw new Error('Expected callback to be a function'); } - if (this.isCancelled) { + if (this._isCancelled) { callback(); return; } @@ -29,7 +29,11 @@ export class CancellationToken { }; public cancel = () => { - this.isCancelled = true; + this._isCancelled = true; this._callbacks.forEach(callback => callback()); }; + + public isCancelled() { + return this._isCancelled; + } } diff --git a/x-pack/legacy/plugins/reporting/common/constants.ts b/x-pack/legacy/plugins/reporting/common/constants.ts index 6f1f43fe11fe5..723320e74bfbd 100644 --- a/x-pack/legacy/plugins/reporting/common/constants.ts +++ b/x-pack/legacy/plugins/reporting/common/constants.ts @@ -6,6 +6,8 @@ export const PLUGIN_ID = 'reporting'; +export const BROWSER_TYPE = 'chromium'; + export const JOB_COMPLETION_NOTIFICATIONS_SESSION_KEY = 'xpack.reporting.jobCompletionNotifications'; diff --git a/x-pack/legacy/plugins/reporting/common/get_absolute_url.ts b/x-pack/legacy/plugins/reporting/common/get_absolute_url.ts index 299cde58891d3..79c5a274d8c77 100644 --- a/x-pack/legacy/plugins/reporting/common/get_absolute_url.ts +++ b/x-pack/legacy/plugins/reporting/common/get_absolute_url.ts @@ -5,12 +5,11 @@ */ import url from 'url'; -// @ts-ignore import { oncePerServer } from '../server/lib/once_per_server'; -import { ConfigObject, KbnServer } from '../types'; +import { ServerFacade } from '../types'; -function getAbsoluteUrlFn(server: KbnServer) { - const config: ConfigObject = server.config(); +function getAbsoluteUrlFn(server: ServerFacade) { + const config = server.config(); return function getAbsoluteUrl({ basePath = config.get('server.basePath'), diff --git a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/decrypt_job_headers.ts b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/decrypt_job_headers.ts index e933056c79441..887f4dbb11be7 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/decrypt_job_headers.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/decrypt_job_headers.ts @@ -5,14 +5,14 @@ */ // @ts-ignore import { cryptoFactory } from '../../../server/lib/crypto'; -import { CryptoFactory, JobDocPayload, KbnServer } from '../../../types'; +import { CryptoFactory, JobDocPayload, ServerFacade } from '../../../types'; export const decryptJobHeaders = async ({ job, server, }: { job: JobDocPayload; - server: KbnServer; + server: ServerFacade; }) => { const crypto: CryptoFactory = cryptoFactory(server); const decryptedHeaders: string = await crypto.decrypt(job.headers); diff --git a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_conditional_headers.ts b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_conditional_headers.ts index 04959712a117a..9522d6c087a89 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_conditional_headers.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_conditional_headers.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 { ConditionalHeaders, ConfigObject, JobDocPayload, KbnServer } from '../../../types'; +import { ConditionalHeaders, JobDocPayload, ServerFacade } from '../../../types'; export const getConditionalHeaders = ({ job, @@ -12,9 +12,9 @@ export const getConditionalHeaders = ({ }: { job: JobDocPayload; filteredHeaders: Record; - server: KbnServer; + server: ServerFacade; }) => { - const config: ConfigObject = server.config(); + const config = server.config(); const [hostname, port, basePath, protocol] = [ config.get('xpack.reporting.kibanaServer.hostname') || config.get('server.host'), config.get('xpack.reporting.kibanaServer.port') || config.get('server.port'), diff --git a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_custom_logo.ts b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_custom_logo.ts index 00cdd0410fac2..749ab3c50f1c0 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_custom_logo.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_custom_logo.ts @@ -5,7 +5,7 @@ */ import { UI_SETTINGS_CUSTOM_PDF_LOGO } from '../../../common/constants'; -import { ConditionalHeaders, KbnServer } from '../../../types'; +import { ConditionalHeaders, ServerFacade } from '../../../types'; import { JobDocPayloadPDF } from '../../printable_pdf/types'; // Logo is PDF only export const getCustomLogo = async ({ @@ -15,7 +15,7 @@ export const getCustomLogo = async ({ }: { job: JobDocPayloadPDF; conditionalHeaders: ConditionalHeaders; - server: KbnServer; + server: ServerFacade; }) => { const serverBasePath: string = server.config().get('server.basePath'); diff --git a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_full_urls.ts b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_full_urls.ts index e1ca599b38d41..3b82852073421 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_full_urls.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_full_urls.ts @@ -12,7 +12,7 @@ import { } from 'url'; import { getAbsoluteUrlFactory } from '../../../common/get_absolute_url'; import { validateUrls } from '../../../common/validate_urls'; -import { KbnServer } from '../../../types'; +import { ServerFacade } from '../../../types'; import { JobDocPayloadPNG } from '../../png/types'; import { JobDocPayloadPDF } from '../../printable_pdf/types'; @@ -26,7 +26,7 @@ export async function getFullUrls({ ...mergeValues // pass-throughs }: { job: JobDocPayloadPNG | JobDocPayloadPDF; - server: KbnServer; + server: ServerFacade; }) { const getAbsoluteUrl = getAbsoluteUrlFactory(server); diff --git a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/omit_blacklisted_headers.ts b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/omit_blacklisted_headers.ts index 44a9ed5a8ee51..a0aa4d973298d 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/omit_blacklisted_headers.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/omit_blacklisted_headers.ts @@ -5,7 +5,7 @@ */ import { omit } from 'lodash'; import { KBN_SCREENSHOT_HEADER_BLACKLIST } from '../../../common/constants'; -import { JobDocPayload, KbnServer } from '../../../types'; +import { JobDocPayload, ServerFacade } from '../../../types'; export const omitBlacklistedHeaders = ({ job, @@ -14,7 +14,7 @@ export const omitBlacklistedHeaders = ({ }: { job: JobDocPayload; decryptedHeaders: Record; - server: KbnServer; + server: ServerFacade; }) => { const filteredHeaders: Record = omit( decryptedHeaders, diff --git a/x-pack/legacy/plugins/reporting/export_types/common/layouts/create_layout.ts b/x-pack/legacy/plugins/reporting/export_types/common/layouts/create_layout.ts index 17c540e807211..0cb83352d4606 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/layouts/create_layout.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/layouts/create_layout.ts @@ -3,13 +3,13 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { KbnServer } from '../../../types'; +import { ServerFacade } from '../../../types'; import { LayoutTypes } from '../constants'; import { Layout, LayoutParams } from './layout'; import { PreserveLayout } from './preserve_layout'; import { PrintLayout } from './print_layout'; -export function createLayout(server: KbnServer, layoutParams?: LayoutParams): Layout { +export function createLayout(server: ServerFacade, layoutParams?: LayoutParams): Layout { if (layoutParams && layoutParams.id === LayoutTypes.PRESERVE_LAYOUT) { return new PreserveLayout(layoutParams.dimensions); } diff --git a/x-pack/legacy/plugins/reporting/export_types/common/layouts/print_layout.ts b/x-pack/legacy/plugins/reporting/export_types/common/layouts/print_layout.ts index 9d672318000c1..44361181e3262 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/layouts/print_layout.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/layouts/print_layout.ts @@ -7,7 +7,7 @@ import path from 'path'; import { EvaluateFn, SerializableOrJSHandle } from 'puppeteer'; import { LevelLogger } from '../../../server/lib'; import { HeadlessChromiumDriver } from '../../../server/browsers/chromium/driver'; -import { KbnServer } from '../../../types'; +import { ServerFacade } from '../../../types'; import { LayoutTypes } from '../constants'; import { getDefaultLayoutSelectors, Layout, LayoutSelectorDictionary, Size } from './layout'; import { CaptureConfig } from './types'; @@ -20,7 +20,7 @@ export class PrintLayout extends Layout { public readonly groupCount = 2; private captureConfig: CaptureConfig; - constructor(server: KbnServer) { + constructor(server: ServerFacade) { super(LayoutTypes.PRINT); this.captureConfig = server.config().get('xpack.reporting.capture'); } diff --git a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/index.ts b/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/index.ts index 9713adc76a5fa..0c63def67bd04 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/index.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/index.ts @@ -6,8 +6,7 @@ import * as Rx from 'rxjs'; import { first, mergeMap } from 'rxjs/operators'; -import { KbnServer } from '../../../../types'; -import { HeadlessChromiumDriverFactory } from '../../../../server/browsers/chromium/driver_factory'; +import { ServerFacade, CaptureConfig } from '../../../../types'; import { HeadlessChromiumDriver as HeadlessBrowser } from '../../../../server/browsers/chromium/driver'; import { ElementsPositionAndAttribute, @@ -29,14 +28,14 @@ import { skipTelemetry } from './skip_telemetry'; // NOTE: Typescript does not throw an error if this interface has errors! interface ScreenshotResults { - timeRange: TimeRange; + timeRang: TimeRange; screenshots: Screenshot[]; } -export function screenshotsObservableFactory(server: KbnServer) { - const browserDriverFactory: HeadlessChromiumDriverFactory = server.plugins.reporting.browserDriverFactory; // prettier-ignore +export function screenshotsObservableFactory(server: ServerFacade) { const config = server.config(); - const captureConfig = config.get('xpack.reporting.capture'); + const captureConfig: CaptureConfig = config.get('xpack.reporting.capture'); + const { browserDriverFactory } = server.plugins.reporting!; return function screenshotsObservable({ logger, diff --git a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/wait_for_render.ts b/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/wait_for_render.ts index b49b52e18ded7..df0d591ff913c 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/wait_for_render.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/wait_for_render.ts @@ -4,12 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ +import { CaptureConfig } from '../../../../types'; import { HeadlessChromiumDriver as HeadlessBrowser } from '../../../../server/browsers/chromium/driver'; import { LevelLogger } from '../../../../server/lib'; import { LayoutInstance } from '../../layouts/layout'; export const waitForRenderComplete = async ( - captureConfig: any, + captureConfig: CaptureConfig, browser: HeadlessBrowser, layout: LayoutInstance, logger: LevelLogger diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/create_job.ts b/x-pack/legacy/plugins/reporting/export_types/csv/server/create_job.ts index 952547790adbb..b1713e1753eea 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/create_job.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/create_job.ts @@ -4,19 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Request } from 'hapi'; import { oncePerServer } from '../../../server/lib/once_per_server'; import { cryptoFactory } from '../../../server/lib/crypto'; -import { KbnServer, ConditionalHeaders, CreateJobFactory } from '../../../types'; +import { ConditionalHeaders, CreateJobFactory, ServerFacade, RequestFacade } from '../../../types'; import { JobParamsDiscoverCsv, ESQueueCreateJobFnDiscoverCsv } from '../types'; -function createJobFn(server: KbnServer) { +function createJobFn(server: ServerFacade) { const crypto = cryptoFactory(server); return async function createJob( jobParams: JobParamsDiscoverCsv, headers: ConditionalHeaders, - request: Request + request: RequestFacade ) { const serializedEncryptedHeaders = await crypto.encrypt(headers); @@ -36,5 +35,5 @@ function createJobFn(server: KbnServer) { } export const createJobFactory: CreateJobFactory = oncePerServer(createJobFn as ( - server: KbnServer + server: ServerFacade ) => ESQueueCreateJobFnDiscoverCsv); diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/hit_iterator.ts b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/hit_iterator.ts new file mode 100644 index 0000000000000..c439c2bbf60eb --- /dev/null +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/__tests__/hit_iterator.ts @@ -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 expect from '@kbn/expect'; +import sinon from 'sinon'; +import { CancellationToken } from '../../../../../common/cancellation_token'; +import { Logger, ScrollConfig } from '../../../../../types'; +import { createHitIterator } from '../hit_iterator'; + +const mockLogger = { + error: new Function(), + debug: new Function(), + warning: new Function(), +} as Logger; +const debugLogStub = sinon.stub(mockLogger, 'debug'); +const warnLogStub = sinon.stub(mockLogger, 'warning'); +const errorLogStub = sinon.stub(mockLogger, 'error'); +const mockCallEndpoint = sinon.stub(); +const mockSearchRequest = {}; +const mockConfig: ScrollConfig = { duration: '2s', size: 123 }; +let realCancellationToken = new CancellationToken(); +let isCancelledStub: sinon.SinonStub; + +describe('hitIterator', function() { + beforeEach(() => { + debugLogStub.resetHistory(); + warnLogStub.resetHistory(); + errorLogStub.resetHistory(); + mockCallEndpoint.resetHistory(); + mockCallEndpoint.resetBehavior(); + mockCallEndpoint.resolves({ _scroll_id: '123blah', hits: { hits: ['you found me'] } }); + mockCallEndpoint.onCall(11).resolves({ _scroll_id: '123blah', hits: {} }); + + isCancelledStub = sinon.stub(realCancellationToken, 'isCancelled'); + isCancelledStub.returns(false); + }); + + afterEach(() => { + realCancellationToken = new CancellationToken(); + }); + + it('iterates hits', async () => { + // Begin + const hitIterator = createHitIterator(mockLogger); + const iterator = hitIterator( + mockConfig, + mockCallEndpoint, + mockSearchRequest, + realCancellationToken + ); + + while (true) { + const { done: iterationDone, value: hit } = await iterator.next(); + if (iterationDone) { + break; + } + expect(hit).to.be('you found me'); + } + + expect(mockCallEndpoint.callCount).to.be(13); + expect(debugLogStub.callCount).to.be(13); + expect(warnLogStub.callCount).to.be(0); + expect(errorLogStub.callCount).to.be(0); + }); + + it('stops searches after cancellation', async () => { + // Setup + isCancelledStub.onFirstCall().returns(false); + isCancelledStub.returns(true); + + // Begin + const hitIterator = createHitIterator(mockLogger); + const iterator = hitIterator( + mockConfig, + mockCallEndpoint, + mockSearchRequest, + realCancellationToken + ); + + while (true) { + const { done: iterationDone, value: hit } = await iterator.next(); + if (iterationDone) { + break; + } + expect(hit).to.be('you found me'); + } + + expect(mockCallEndpoint.callCount).to.be(3); + expect(debugLogStub.callCount).to.be(3); + expect(warnLogStub.callCount).to.be(1); + expect(errorLogStub.callCount).to.be(0); + + expect(warnLogStub.firstCall.lastArg).to.be( + 'Any remaining scrolling searches have been cancelled by the cancellation token.' + ); + }); + + it('handles time out', async () => { + // Setup + mockCallEndpoint.onCall(2).resolves({ status: 404 }); + + // Begin + const hitIterator = createHitIterator(mockLogger); + const iterator = hitIterator( + mockConfig, + mockCallEndpoint, + mockSearchRequest, + realCancellationToken + ); + + let errorThrown = false; + try { + while (true) { + const { done: iterationDone, value: hit } = await iterator.next(); + if (iterationDone) { + break; + } + expect(hit).to.be('you found me'); + } + } catch (err) { + expect(err).to.eql( + new Error('Expected _scroll_id in the following Elasticsearch response: {"status":404}') + ); + errorThrown = true; + } + + expect(mockCallEndpoint.callCount).to.be(4); + expect(debugLogStub.callCount).to.be(4); + expect(warnLogStub.callCount).to.be(0); + expect(errorLogStub.callCount).to.be(1); + expect(errorThrown).to.be(true); + }); +}); diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/hit_iterator.js b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/hit_iterator.js deleted file mode 100644 index 5ad6182568721..0000000000000 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/hit_iterator.js +++ /dev/null @@ -1,71 +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 { i18n } from '@kbn/i18n'; - -async function parseResponse(request) { - const response = await request; - if (!response._scroll_id) { - throw new Error(i18n.translate('xpack.reporting.exportTypes.csv.hitIterator.expectedScrollIdErrorMessage', { - defaultMessage: 'Expected {scrollId} in the following Elasticsearch response: {response}', - values: { response: JSON.stringify(response), scrollId: '_scroll_id' } - })); - } - - if (!response.hits) { - throw new Error(i18n.translate('xpack.reporting.exportTypes.csv.hitIterator.expectedHitsErrorMessage', { - defaultMessage: 'Expected {hits} in the following Elasticsearch response: {response}', - values: { response: JSON.stringify(response), hits: 'hits' } - })); - } - - return { - scrollId: response._scroll_id, - hits: response.hits.hits - }; -} - -export function createHitIterator(logger) { - return async function* hitIterator(scrollSettings, callEndpoint, searchRequest, cancellationToken) { - logger.debug('executing search request'); - function search(index, body) { - return parseResponse(callEndpoint('search', { - index, - body, - scroll: scrollSettings.duration, - size: scrollSettings.size - })); - } - - function scroll(scrollId) { - logger.debug('executing scroll request'); - return parseResponse(callEndpoint('scroll', { - scrollId, - scroll: scrollSettings.duration - })); - } - - function clearScroll(scrollId) { - logger.debug('executing clearScroll request'); - return callEndpoint('clearScroll', { - scrollId: [ scrollId ] - }); - } - - let { scrollId, hits } = await search(searchRequest.index, searchRequest.body); - try { - while(hits.length && !cancellationToken.isCancelled) { - for(const hit of hits) { - yield hit; - } - - ({ scrollId, hits } = await scroll(scrollId)); - } - } finally { - await clearScroll(scrollId); - } - }; -} diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/hit_iterator.ts b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/hit_iterator.ts new file mode 100644 index 0000000000000..68836c01369e3 --- /dev/null +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/lib/hit_iterator.ts @@ -0,0 +1,97 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { SearchParams, SearchResponse } from 'elasticsearch'; + +import { i18n } from '@kbn/i18n'; +import { CancellationToken, ScrollConfig, Logger } from '../../../../types'; + +async function parseResponse(request: SearchResponse) { + const response = await request; + if (!response || !response._scroll_id) { + throw new Error( + i18n.translate('xpack.reporting.exportTypes.csv.hitIterator.expectedScrollIdErrorMessage', { + defaultMessage: 'Expected {scrollId} in the following Elasticsearch response: {response}', + values: { response: JSON.stringify(response), scrollId: '_scroll_id' }, + }) + ); + } + + if (!response.hits) { + throw new Error( + i18n.translate('xpack.reporting.exportTypes.csv.hitIterator.expectedHitsErrorMessage', { + defaultMessage: 'Expected {hits} in the following Elasticsearch response: {response}', + values: { response: JSON.stringify(response), hits: 'hits' }, + }) + ); + } + + return { + scrollId: response._scroll_id, + hits: response.hits.hits, + }; +} + +export function createHitIterator(logger: Logger) { + return async function* hitIterator( + scrollSettings: ScrollConfig, + callEndpoint: Function, + searchRequest: SearchParams, + cancellationToken: CancellationToken + ) { + logger.debug('executing search request'); + function search(index: string | boolean | string[] | undefined, body: object) { + return parseResponse( + callEndpoint('search', { + index, + body, + scroll: scrollSettings.duration, + size: scrollSettings.size, + }) + ); + } + + function scroll(scrollId: string | undefined) { + logger.debug('executing scroll request'); + return parseResponse( + callEndpoint('scroll', { + scrollId, + scroll: scrollSettings.duration, + }) + ); + } + + function clearScroll(scrollId: string | undefined) { + logger.debug('executing clearScroll request'); + return callEndpoint('clearScroll', { + scrollId: [scrollId], + }); + } + + try { + let { scrollId, hits } = await search(searchRequest.index, searchRequest.body); + try { + while (hits && hits.length && !cancellationToken.isCancelled()) { + for (const hit of hits) { + yield hit; + } + + ({ scrollId, hits } = await scroll(scrollId)); + + if (cancellationToken.isCancelled()) { + logger.warning( + 'Any remaining scrolling searches have been cancelled by the cancellation token.' + ); + } + } + } finally { + await clearScroll(scrollId); + } + } catch (err) { + logger.error(err); + throw err; + } + }; +} diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/types.d.ts b/x-pack/legacy/plugins/reporting/export_types/csv/types.d.ts index 3906c2bb1449d..09f32833915b6 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/types.d.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv/types.d.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Request } from 'hapi'; -import { JobDocPayload, JobParamPostPayload, ConditionalHeaders } from '../../types'; +import { JobDocPayload, JobParamPostPayload, ConditionalHeaders, RequestFacade } from '../../types'; export interface JobParamPostPayloadDiscoverCsv extends JobParamPostPayload { state?: { @@ -30,5 +29,5 @@ export interface JobDocPayloadDiscoverCsv extends JobDocPayload { export type ESQueueCreateJobFnDiscoverCsv = ( jobParams: JobParamsDiscoverCsv, headers: ConditionalHeaders, - request: Request + request: RequestFacade ) => Promise; diff --git a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/create_job/create_job.ts b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/create_job/create_job.ts index 07fdf42a15171..0bcfc6f1ca07c 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/create_job/create_job.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/create_job/create_job.ts @@ -5,12 +5,10 @@ */ import { notFound, notImplemented } from 'boom'; -import { Request } from 'hapi'; import { get } from 'lodash'; - import { PLUGIN_ID, CSV_FROM_SAVEDOBJECT_JOB_TYPE } from '../../../../common/constants'; import { cryptoFactory, LevelLogger, oncePerServer } from '../../../../server/lib'; -import { KbnServer } from '../../../../types'; +import { ServerFacade, RequestFacade } from '../../../../types'; import { SavedObject, SavedObjectServiceError, @@ -32,10 +30,10 @@ interface VisData { type CreateJobFn = ( jobParams: JobParamsPanelCsv, headers: any, - req: Request + req: RequestFacade ) => Promise; -function createJobFn(server: KbnServer): CreateJobFn { +function createJobFn(server: ServerFacade): CreateJobFn { const crypto = cryptoFactory(server); const logger = LevelLogger.createForServer(server, [ PLUGIN_ID, @@ -46,7 +44,7 @@ function createJobFn(server: KbnServer): CreateJobFn { return async function createJob( jobParams: JobParamsPanelCsv, headers: any, - req: Request + req: RequestFacade ): Promise { const { savedObjectType, savedObjectId } = jobParams; const serializedEncryptedHeaders = await crypto.encrypt(headers); diff --git a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/execute_job.ts b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/execute_job.ts index ece5217a56205..9d4bcf1e4b27a 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/execute_job.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/execute_job.ts @@ -4,11 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Request } from 'hapi'; import { i18n } from '@kbn/i18n'; - import { cryptoFactory, LevelLogger, oncePerServer } from '../../../server/lib'; -import { JobDocOutputExecuted, KbnServer, ExecuteImmediateJobFactory } from '../../../types'; +import { + JobDocOutputExecuted, + ServerFacade, + ExecuteImmediateJobFactory, + RequestFacade, +} from '../../../types'; import { CONTENT_TYPE_CSV, CSV_FROM_SAVEDOBJECT_JOB_TYPE, @@ -20,10 +23,10 @@ import { createGenerateCsv } from './lib'; type ExecuteJobFn = ( jobId: string | null, job: JobDocPayloadPanelCsv, - realRequest?: Request + realRequest?: RequestFacade ) => Promise; -function executeJobFactoryFn(server: KbnServer): ExecuteJobFn { +function executeJobFactoryFn(server: ServerFacade): ExecuteJobFn { const crypto = cryptoFactory(server); const logger = LevelLogger.createForServer(server, [ PLUGIN_ID, @@ -34,7 +37,7 @@ function executeJobFactoryFn(server: KbnServer): ExecuteJobFn { return async function executeJob( jobId: string | null, job: JobDocPayloadPanelCsv, - realRequest?: Request + realRequest?: RequestFacade ): Promise { // There will not be a jobID for "immediate" generation. // jobID is only for "queued" jobs @@ -46,7 +49,7 @@ function executeJobFactoryFn(server: KbnServer): ExecuteJobFn { jobLogger.debug(`Execute job generating [${visType}] csv`); - let requestObject: Request | FakeRequest; + let requestObject: RequestFacade | FakeRequest; if (isImmediate && realRequest) { jobLogger.info(`Executing job from immediate API`); requestObject = realRequest; diff --git a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv.ts b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv.ts index aafad4674a1b2..6a4c6df5e1ace 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv.ts @@ -5,16 +5,14 @@ */ import { badRequest } from 'boom'; -import { Request } from 'hapi'; -import { KbnServer, Logger } from '../../../../types'; -import { SearchPanel, VisPanel, JobParamsPanelCsv } from '../../types'; -import { FakeRequest } from '../../types'; +import { ServerFacade, RequestFacade, Logger } from '../../../../types'; +import { SearchPanel, VisPanel, JobParamsPanelCsv, FakeRequest } from '../../types'; import { generateCsvSearch } from './generate_csv_search'; export function createGenerateCsv(logger: Logger) { return async function generateCsv( - request: Request | FakeRequest, - server: KbnServer, + request: RequestFacade | FakeRequest, + server: ServerFacade, visType: string, panel: VisPanel | SearchPanel, jobParams: JobParamsPanelCsv @@ -27,7 +25,7 @@ export function createGenerateCsv(logger: Logger) { switch (visType) { case 'search': return await generateCsvSearch( - request as Request, + request as RequestFacade, server, logger, panel as SearchPanel, diff --git a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv_search.ts b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv_search.ts index da2c184d2d6e0..2e2c8635fca5f 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv_search.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv_search.ts @@ -4,15 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Request } from 'hapi'; - import { buildEsQuery } from '@kbn/es-query'; // @ts-ignore no module definition import { createGenerateCsv } from '../../../csv/server/lib/generate_csv'; - import { CancellationToken } from '../../../../common/cancellation_token'; - -import { KbnServer, Logger } from '../../../../types'; +import { ServerFacade, RequestFacade, Logger } from '../../../../types'; import { IndexPatternSavedObject, SavedSearchObjectAttributes, @@ -50,8 +46,8 @@ const getUiSettings = async (config: any) => { }; export async function generateCsvSearch( - req: Request, - server: KbnServer, + req: RequestFacade, + server: ServerFacade, logger: Logger, searchPanel: SearchPanel, jobParams: JobParamsDiscoverCsv @@ -140,7 +136,7 @@ export async function generateCsvSearch( }, }; const { callWithRequest } = server.plugins.elasticsearch.getCluster('data'); - const callCluster = (...params: any[]) => callWithRequest(req, ...params); + const callCluster = (...params: [string, object]) => callWithRequest(req, ...params); const config = server.config(); const uiSettings = await getUiSettings(uiConfig); diff --git a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/get_job_params_from_request.ts b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/get_job_params_from_request.ts index 2041b1253ee51..774e430d593cd 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/get_job_params_from_request.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/get_job_params_from_request.ts @@ -4,11 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Request } from 'hapi'; +import { RequestFacade } from '../../../../types'; import { JobParamsPostPayloadPanelCsv, JobParamsPanelCsv } from '../../types'; export function getJobParamsFromRequest( - request: Request, + request: RequestFacade, opts: { isImmediate: boolean } ): Partial { const { savedObjectType, savedObjectId } = request.params; diff --git a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/types.d.ts b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/types.d.ts index aa681210fc740..f8692c336b292 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/types.d.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/types.d.ts @@ -5,11 +5,11 @@ */ import { CancellationToken } from '../../common/cancellation_token'; -import { JobParamPostPayload, JobDocPayload, KbnServer } from '../../types'; +import { JobParamPostPayload, JobDocPayload, ServerFacade } from '../../types'; export interface FakeRequest { headers: any; - server: KbnServer; + server: ServerFacade; } export interface JobParamsPostPayloadPanelCsv extends JobParamPostPayload { diff --git a/x-pack/legacy/plugins/reporting/export_types/png/server/create_job/index.ts b/x-pack/legacy/plugins/reporting/export_types/png/server/create_job/index.ts index 2ee1e7a874355..4176a1351d654 100644 --- a/x-pack/legacy/plugins/reporting/export_types/png/server/create_job/index.ts +++ b/x-pack/legacy/plugins/reporting/export_types/png/server/create_job/index.ts @@ -4,20 +4,24 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Request } from 'hapi'; -import { KbnServer, ConditionalHeaders, CreateJobFactory } from '../../../../types'; +import { + ServerFacade, + RequestFacade, + ConditionalHeaders, + CreateJobFactory, +} from '../../../../types'; import { validateUrls } from '../../../../common/validate_urls'; import { cryptoFactory } from '../../../../server/lib/crypto'; import { oncePerServer } from '../../../../server/lib/once_per_server'; import { JobParamsPNG, ESQueueCreateJobFnPNG } from '../../types'; -function createJobFn(server: KbnServer) { +function createJobFn(server: ServerFacade) { const crypto = cryptoFactory(server); return async function createJob( { objectType, title, relativeUrl, browserTimezone, layout }: JobParamsPNG, headers: ConditionalHeaders, - request: Request + request: RequestFacade ) { const serializedEncryptedHeaders = await crypto.encrypt(headers); @@ -37,5 +41,5 @@ function createJobFn(server: KbnServer) { } export const createJobFactory: CreateJobFactory = oncePerServer(createJobFn as ( - server: KbnServer + server: ServerFacade ) => ESQueueCreateJobFnPNG); diff --git a/x-pack/legacy/plugins/reporting/export_types/png/server/lib/generate_png.ts b/x-pack/legacy/plugins/reporting/export_types/png/server/lib/generate_png.ts index 3d09d2dae42a1..5980f1884ab2f 100644 --- a/x-pack/legacy/plugins/reporting/export_types/png/server/lib/generate_png.ts +++ b/x-pack/legacy/plugins/reporting/export_types/png/server/lib/generate_png.ts @@ -7,7 +7,7 @@ import * as Rx from 'rxjs'; import { toArray, mergeMap } from 'rxjs/operators'; import { LevelLogger } from '../../../../server/lib'; -import { KbnServer, ConditionalHeaders } from '../../../../types'; +import { ServerFacade, ConditionalHeaders } from '../../../../types'; import { oncePerServer } from '../../../../server/lib/once_per_server'; import { screenshotsObservableFactory } from '../../../common/lib/screenshots'; import { PreserveLayout } from '../../../common/layouts/preserve_layout'; @@ -21,7 +21,7 @@ interface UrlScreenshot { screenshots: ScreenshotData[]; } -function generatePngObservableFn(server: KbnServer) { +function generatePngObservableFn(server: ServerFacade) { const screenshotsObservable = screenshotsObservableFactory(server); const captureConcurrency = 1; diff --git a/x-pack/legacy/plugins/reporting/export_types/png/types.d.ts b/x-pack/legacy/plugins/reporting/export_types/png/types.d.ts index f0590f98da362..c7ea2bdfba59f 100644 --- a/x-pack/legacy/plugins/reporting/export_types/png/types.d.ts +++ b/x-pack/legacy/plugins/reporting/export_types/png/types.d.ts @@ -4,9 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Request } from 'hapi'; import { LayoutInstance } from '../common/layouts/layout'; -import { ConditionalHeaders, KbnServer, JobDocPayload } from '../../types'; +import { ConditionalHeaders, JobDocPayload, RequestFacade } from '../../types'; // Job params: structure of incoming user request data export interface JobParamsPNG { @@ -30,5 +29,5 @@ export interface JobDocPayloadPNG extends JobDocPayload { export type ESQueueCreateJobFnPNG = ( jobParams: JobParamsPNG, headers: ConditionalHeaders, - request: Request + request: RequestFacade ) => Promise; diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/generate_pdf.ts b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/generate_pdf.ts index e57e18eff1b67..0d2243acfef9b 100644 --- a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/generate_pdf.ts +++ b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/generate_pdf.ts @@ -8,7 +8,7 @@ import * as Rx from 'rxjs'; import { toArray, mergeMap } from 'rxjs/operators'; import { groupBy } from 'lodash'; import { LevelLogger } from '../../../../server/lib'; -import { KbnServer, ConditionalHeaders } from '../../../../types'; +import { ServerFacade, ConditionalHeaders } from '../../../../types'; // @ts-ignore untyped module import { pdf } from './pdf'; import { oncePerServer } from '../../../../server/lib/once_per_server'; @@ -38,7 +38,7 @@ const getTimeRange = (urlScreenshots: UrlScreenshot[]) => { return null; }; -function generatePdfObservableFn(server: KbnServer) { +function generatePdfObservableFn(server: ServerFacade) { const screenshotsObservable = screenshotsObservableFactory(server); const captureConcurrency = 1; diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/types.d.ts b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/types.d.ts index a27b4aed48760..d1e5e58f0f0a5 100644 --- a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/types.d.ts +++ b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/types.d.ts @@ -4,9 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Request } from 'hapi'; import { LayoutInstance } from '../common/layouts/layout'; -import { ConditionalHeaders, KbnServer, JobDocPayload } from '../../types'; +import { ConditionalHeaders, JobDocPayload, ServerFacade, RequestFacade } from '../../types'; // Job params: structure of incoming user request data, after being parsed from RISON export interface JobParamsPDF { @@ -32,7 +31,7 @@ export interface JobDocPayloadPDF extends JobDocPayload { export type ESQueueCreateJobFnPDF = ( jobParams: JobParamsPDF, headers: ConditionalHeaders, - request: Request + request: RequestFacade ) => Promise; -export type CreateJobFactoryPDF = (server: KbnServer) => ESQueueCreateJobFnPDF; +export type CreateJobFactoryPDF = (server: ServerFacade) => ESQueueCreateJobFnPDF; diff --git a/x-pack/legacy/plugins/reporting/index.js b/x-pack/legacy/plugins/reporting/index.js index 95c792d9d5171..1fe83124e2b45 100644 --- a/x-pack/legacy/plugins/reporting/index.js +++ b/x-pack/legacy/plugins/reporting/index.js @@ -5,7 +5,7 @@ */ import { resolve } from 'path'; -import { PLUGIN_ID, UI_SETTINGS_CUSTOM_PDF_LOGO } from './common/constants'; +import { PLUGIN_ID, BROWSER_TYPE, UI_SETTINGS_CUSTOM_PDF_LOGO } from './common/constants'; import { mirrorPluginStatus } from '../../server/lib/mirror_plugin_status'; import { registerRoutes } from './server/routes'; import { @@ -16,13 +16,8 @@ import { runValidations, } from './server/lib'; import { config as appConfig } from './server/config/config'; -import { - CHROMIUM, - createBrowserDriverFactory, - getDefaultChromiumSandboxDisabled, -} from './server/browsers'; +import { createBrowserDriverFactory, getDefaultChromiumSandboxDisabled } from './server/browsers'; import { logConfiguration } from './log_configuration'; - import { getReportingUsageCollector } from './server/usage'; import { i18n } from '@kbn/i18n'; @@ -116,7 +111,7 @@ export const reporting = (kibana) => { settleTime: Joi.number().integer().default(1000), //deprecated concurrency: Joi.number().integer().default(appConfig.concurrency), //deprecated browser: Joi.object({ - type: Joi.any().valid(CHROMIUM).default(CHROMIUM), + type: Joi.any().valid(BROWSER_TYPE).default(BROWSER_TYPE), autoDownload: Joi.boolean().when('$dist', { is: true, then: Joi.default(false), @@ -182,22 +177,23 @@ export const reporting = (kibana) => { }).default(); }, + // TODO: Decouple Hapi: Build a server facade object based on the server to + // pass through to the libs. Do not pass server directly init: async function (server) { let isCollectorReady = false; const isReady = () => isCollectorReady; // Register a function with server to manage the collection of usage stats server.usage.collectorSet.register(getReportingUsageCollector(server, isReady)); - const logger = LevelLogger.createForServer(server, [PLUGIN_ID], true); + const logger = LevelLogger.createForServer(server, [PLUGIN_ID]); const [exportTypesRegistry, browserFactory] = await Promise.all([ exportTypesRegistryFactory(server), createBrowserDriverFactory(server), ]); server.expose('exportTypesRegistry', exportTypesRegistry); - const config = server.config(); - logConfiguration(config, logger); - runValidations(server, config, logger, browserFactory); + logConfiguration(server, logger); + runValidations(server, logger, browserFactory); const { xpack_main: xpackMainPlugin } = server.plugins; mirrorPluginStatus(xpackMainPlugin, this); diff --git a/x-pack/legacy/plugins/reporting/log_configuration.js b/x-pack/legacy/plugins/reporting/log_configuration.js index ef4dab2ee3be7..94ff90f5944d0 100644 --- a/x-pack/legacy/plugins/reporting/log_configuration.js +++ b/x-pack/legacy/plugins/reporting/log_configuration.js @@ -9,7 +9,9 @@ import { promisify } from 'util'; const getos = promisify(getosSync); -export async function logConfiguration(config, logger) { +export async function logConfiguration(server, logger) { + const config = server.config(); + const browserType = config.get('xpack.reporting.capture.browser.type'); logger.debug(`Browser type: ${browserType}`); diff --git a/x-pack/legacy/plugins/reporting/public/components/__snapshots__/report_listing.test.tsx.snap b/x-pack/legacy/plugins/reporting/public/components/__snapshots__/report_listing.test.tsx.snap index 4e861a5b8d1d5..3a8d2bc13249e 100644 --- a/x-pack/legacy/plugins/reporting/public/components/__snapshots__/report_listing.test.tsx.snap +++ b/x-pack/legacy/plugins/reporting/public/components/__snapshots__/report_listing.test.tsx.snap @@ -112,6 +112,11 @@ Array [ data-test-subj="tableHeaderCell_object_title_0" role="columnheader" scope="col" + style={ + Object { + "width": undefined, + } + } >

{ - if (err) { - return this.logger.error( - `error deleting user data directory at [${userDataDir}]: [${err}]` - ); - } + del(userDataDir).catch(error => { + this.logger.error(`error deleting user data directory at [${userDataDir}]: [${error}]`); }); }); }); diff --git a/x-pack/legacy/plugins/reporting/server/browsers/create_browser_driver_factory.ts b/x-pack/legacy/plugins/reporting/server/browsers/create_browser_driver_factory.ts index de4312ea1bef4..a253988b01952 100644 --- a/x-pack/legacy/plugins/reporting/server/browsers/create_browser_driver_factory.ts +++ b/x-pack/legacy/plugins/reporting/server/browsers/create_browser_driver_factory.ts @@ -4,44 +4,43 @@ * you may not use this file except in compliance with the Elastic License. */ -import { BROWSERS_BY_TYPE, BrowserType } from './browsers'; import { ensureBrowserDownloaded } from './download'; import { installBrowser } from './install'; import { LevelLogger } from '../lib/level_logger'; -import { KbnServer } from '../../types'; -import { PLUGIN_ID } from '../../common/constants'; +import { ServerFacade, CaptureConfig } from '../../types'; +import { PLUGIN_ID, BROWSER_TYPE } from '../../common/constants'; +import { chromium } from './index'; import { HeadlessChromiumDriverFactory } from './chromium/driver_factory'; export async function createBrowserDriverFactory( - server: KbnServer + server: ServerFacade ): Promise { const config = server.config(); const logger = LevelLogger.createForServer(server, [PLUGIN_ID, 'browser-driver']); - const DATA_DIR = config.get('path.data'); - const CAPTURE_CONFIG = config.get('xpack.reporting.capture'); - const BROWSER_TYPE: BrowserType = CAPTURE_CONFIG.browser.type; - const BROWSER_AUTO_DOWNLOAD = CAPTURE_CONFIG.browser.autoDownload; - const BROWSER_CONFIG = CAPTURE_CONFIG.browser[BROWSER_TYPE]; - const NETWORK_POLICY = CAPTURE_CONFIG.networkPolicy; - const REPORTING_TIMEOUT = config.get('xpack.reporting.queue.timeout'); + const dataDir: string = config.get('path.data'); + const captureConfig: CaptureConfig = config.get('xpack.reporting.capture'); + const browserType = captureConfig.browser.type; + const browserAutoDownload = captureConfig.browser.autoDownload; + const browserConfig = captureConfig.browser[BROWSER_TYPE]; + const networkPolicy = captureConfig.networkPolicy; + const reportingTimeout: number = config.get('xpack.reporting.queue.timeout'); - if (BROWSER_CONFIG.disableSandbox) { + if (browserConfig.disableSandbox) { logger.warning(`Enabling the Chromium sandbox provides an additional layer of protection.`); } - if (BROWSER_AUTO_DOWNLOAD) { - await ensureBrowserDownloaded(BROWSER_TYPE); + if (browserAutoDownload) { + await ensureBrowserDownloaded(browserType); } try { - const browser = BROWSERS_BY_TYPE[BROWSER_TYPE]; // NOTE: unecessary indirection: this is always a Chromium browser object, as of PhantomJS removal - const { binaryPath } = await installBrowser(logger, browser, DATA_DIR); - return browser.createDriverFactory( + const { binaryPath } = await installBrowser(logger, chromium, dataDir); + return chromium.createDriverFactory( binaryPath, logger, - BROWSER_CONFIG, - REPORTING_TIMEOUT, - NETWORK_POLICY + browserConfig, + reportingTimeout, + networkPolicy ); } catch (error) { if (error.cause && ['EACCES', 'EEXIST'].includes(error.cause.code)) { diff --git a/x-pack/legacy/plugins/reporting/server/browsers/download/clean.ts b/x-pack/legacy/plugins/reporting/server/browsers/download/clean.ts index 394b76d772531..4355a6a0a1773 100644 --- a/x-pack/legacy/plugins/reporting/server/browsers/download/clean.ts +++ b/x-pack/legacy/plugins/reporting/server/browsers/download/clean.ts @@ -7,8 +7,7 @@ import { readdirSync } from 'fs'; import { resolve as resolvePath } from 'path'; -import { fromNode as fcb } from 'bluebird'; -import rimraf from 'rimraf'; +import del from 'del'; import { log, asyncMap } from './util'; @@ -32,7 +31,7 @@ export async function clean(dir: string, expectedPaths: string[]) { const path = resolvePath(dir, filename); if (!expectedPaths.includes(path)) { log(`Deleting unexpected file ${path}`); - await fcb(cb => rimraf(path, cb)); + await del(path); } }); } diff --git a/x-pack/legacy/plugins/reporting/server/browsers/download/ensure_downloaded.ts b/x-pack/legacy/plugins/reporting/server/browsers/download/ensure_downloaded.ts index d322566f9aa1d..c79d2c263b2e1 100644 --- a/x-pack/legacy/plugins/reporting/server/browsers/download/ensure_downloaded.ts +++ b/x-pack/legacy/plugins/reporting/server/browsers/download/ensure_downloaded.ts @@ -7,7 +7,8 @@ import { resolve as resolvePath } from 'path'; import { existsSync } from 'fs'; -import { BROWSERS_BY_TYPE, BrowserType } from '../browsers'; +import { chromium } from '../index'; +import { BrowserType } from '../types'; import { md5 } from './checksum'; import { asyncMap } from './util'; @@ -21,7 +22,7 @@ import { clean } from './clean'; * @return {Promise} */ export async function ensureBrowserDownloaded(browserType: BrowserType) { - await ensureDownloaded([BROWSERS_BY_TYPE[browserType]]); + await ensureDownloaded([chromium]); } /** @@ -29,7 +30,7 @@ export async function ensureBrowserDownloaded(browserType: BrowserType) { * @return {Promise} */ export async function ensureAllBrowsersDownloaded() { - await ensureDownloaded(Object.values(BROWSERS_BY_TYPE)); + await ensureDownloaded([chromium]); } /** diff --git a/x-pack/legacy/plugins/reporting/server/browsers/index.ts b/x-pack/legacy/plugins/reporting/server/browsers/index.ts index 3f97a1368246b..a5ecc405bf9c5 100644 --- a/x-pack/legacy/plugins/reporting/server/browsers/index.ts +++ b/x-pack/legacy/plugins/reporting/server/browsers/index.ts @@ -3,8 +3,13 @@ * 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 chromiumDefinition from './chromium'; export { ensureAllBrowsersDownloaded } from './download'; export { createBrowserDriverFactory } from './create_browser_driver_factory'; export { getDefaultChromiumSandboxDisabled } from './default_chromium_sandbox_disabled'; -export { CHROMIUM } from './browser_types'; + +export const chromium = { + paths: chromiumDefinition.paths, + createDriverFactory: chromiumDefinition.createDriverFactory, +}; diff --git a/x-pack/legacy/plugins/reporting/server/browsers/browser_types.ts b/x-pack/legacy/plugins/reporting/server/browsers/types.d.ts similarity index 86% rename from x-pack/legacy/plugins/reporting/server/browsers/browser_types.ts rename to x-pack/legacy/plugins/reporting/server/browsers/types.d.ts index 0d67678c183ad..43cbb0f13a5a7 100644 --- a/x-pack/legacy/plugins/reporting/server/browsers/browser_types.ts +++ b/x-pack/legacy/plugins/reporting/server/browsers/types.d.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export const CHROMIUM = 'chromium'; +export type BrowserType = 'chromium'; diff --git a/x-pack/legacy/plugins/reporting/server/lib/create_queue.ts b/x-pack/legacy/plugins/reporting/server/lib/create_queue.ts index 9519b7170d76f..a7e81093c136a 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/create_queue.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/create_queue.ts @@ -5,7 +5,7 @@ */ import { PLUGIN_ID } from '../../common/constants'; -import { KbnServer } from '../../types'; +import { ServerFacade, QueueConfig } from '../../types'; // @ts-ignore import { Esqueue } from './esqueue'; import { createWorkerFactory } from './create_worker'; @@ -14,8 +14,8 @@ import { LevelLogger } from './level_logger'; // @ts-ignore import { createTaggedLogger } from './create_tagged_logger'; // TODO remove createTaggedLogger once esqueue is removed -function createQueueFn(server: KbnServer): Esqueue { - const queueConfig = server.config().get('xpack.reporting.queue'); +function createQueueFn(server: ServerFacade): Esqueue { + const queueConfig: QueueConfig = server.config().get('xpack.reporting.queue'); const index = server.config().get('xpack.reporting.index'); const queueOptions = { diff --git a/x-pack/legacy/plugins/reporting/server/lib/create_worker.test.ts b/x-pack/legacy/plugins/reporting/server/lib/create_worker.test.ts index 28997ce0a388d..afad8f096a8bb 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/create_worker.test.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/create_worker.test.ts @@ -5,7 +5,7 @@ */ import * as sinon from 'sinon'; -import { KbnServer } from '../../types'; +import { ServerFacade } from '../../types'; import { createWorkerFactory } from './create_worker'; // @ts-ignore import { Esqueue } from './esqueue'; @@ -24,13 +24,13 @@ const executeJobFactoryStub = sinon.stub(); const getMockServer = ( exportTypes: any[] = [{ executeJobFactory: executeJobFactoryStub }] -): KbnServer => { +): ServerFacade => { return ({ log: sinon.stub(), expose: sinon.stub(), config: () => ({ get: configGetStub }), plugins: { reporting: { exportTypesRegistry: { getAll: () => exportTypes } } }, - } as unknown) as KbnServer; + } as unknown) as ServerFacade; }; describe('Create Worker', () => { diff --git a/x-pack/legacy/plugins/reporting/server/lib/create_worker.ts b/x-pack/legacy/plugins/reporting/server/lib/create_worker.ts index 0e53fe23a4f29..7166659487c9b 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/create_worker.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/create_worker.ts @@ -7,42 +7,50 @@ import { PLUGIN_ID } from '../../common/constants'; import { ESQueueInstance, + QueueConfig, + ExportTypeDefinition, ESQueueWorkerExecuteFn, - ExportType, + ImmediateExecuteFn, JobDoc, + JobDocPayload, JobSource, - KbnServer, + ServerFacade, } from '../../types'; // @ts-ignore untyped dependency import { events as esqueueEvents } from './esqueue'; import { LevelLogger } from './level_logger'; import { oncePerServer } from './once_per_server'; -function createWorkerFn(server: KbnServer) { +function createWorkerFn(server: ServerFacade) { const config = server.config(); - const queueConfig = config.get('xpack.reporting.queue'); - const kibanaName = config.get('server.name'); - const kibanaId = config.get('server.uuid'); - const exportTypesRegistry = server.plugins.reporting.exportTypesRegistry; const logger = LevelLogger.createForServer(server, [PLUGIN_ID, 'queue-worker']); + const queueConfig: QueueConfig = config.get('xpack.reporting.queue'); + const kibanaName: string = config.get('server.name'); + const kibanaId: string = config.get('server.uuid'); + const { exportTypesRegistry } = server.plugins.reporting!; // Once more document types are added, this will need to be passed in return function createWorker(queue: ESQueueInstance) { // export type / execute job map - const jobExectors: Map = new Map(); + const jobExecutors: Map = new Map(); - for (const exportType of exportTypesRegistry.getAll() as ExportType[]) { - const executeJob = exportType.executeJobFactory(server); - jobExectors.set(exportType.jobType, executeJob); + for (const exportType of exportTypesRegistry.getAll() as ExportTypeDefinition[]) { + const executeJobFactory = exportType.executeJobFactory(server); + jobExecutors.set(exportType.jobType, executeJobFactory); } - const workerFn = (job: JobSource, jobdoc: JobDoc, cancellationToken: any) => { + const workerFn = (job: JobSource, jobdoc: JobDocPayload | JobDoc, cancellationToken?: any) => { // pass the work to the jobExecutor - const jobExecutor = jobExectors.get(job._source.jobtype); - if (!jobExecutor) { + if (!jobExecutors.get(job._source.jobtype)) { throw new Error(`Unable to find a job executor for the claimed job: [${job._id}]`); } - return jobExecutor(job._id, jobdoc, cancellationToken); + if (job._id) { + const jobExecutor = jobExecutors.get(job._source.jobtype) as ESQueueWorkerExecuteFn; + return jobExecutor(job._id, jobdoc as JobDoc, cancellationToken); + } else { + const jobExecutor = jobExecutors.get(job._source.jobtype) as ImmediateExecuteFn; + return jobExecutor(null, jobdoc as JobDocPayload, cancellationToken); + } }; const workerOptions = { kibanaName, diff --git a/x-pack/legacy/plugins/reporting/server/lib/crypto.ts b/x-pack/legacy/plugins/reporting/server/lib/crypto.ts index 60e558479af5e..dbc01fc947f8b 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/crypto.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/crypto.ts @@ -6,9 +6,9 @@ import nodeCrypto from '@elastic/node-crypto'; import { oncePerServer } from './once_per_server'; -import { KbnServer } from '../../types'; +import { ServerFacade } from '../../types'; -function cryptoFn(server: KbnServer) { +function cryptoFn(server: ServerFacade) { const encryptionKey = server.config().get('xpack.reporting.encryptionKey'); return nodeCrypto({ encryptionKey }); } diff --git a/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts b/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts index ec0ffdf51733e..c264c0ca7e0eb 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/enqueue_job.ts @@ -4,12 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Request } from 'hapi'; import { get } from 'lodash'; // @ts-ignore import { events as esqueueEvents } from './esqueue'; import { oncePerServer } from './once_per_server'; -import { KbnServer, Logger, ConditionalHeaders } from '../../types'; +import { + ServerFacade, + RequestFacade, + Logger, + CaptureConfig, + QueueConfig, + ConditionalHeaders, +} from '../../types'; interface ConfirmedJob { id: string; @@ -18,13 +24,13 @@ interface ConfirmedJob { _primary_term: number; } -function enqueueJobFn(server: KbnServer) { - const jobQueue = server.plugins.reporting.queue; +function enqueueJobFn(server: ServerFacade) { const config = server.config(); - const queueConfig = config.get('xpack.reporting.queue'); - const browserType = config.get('xpack.reporting.capture.browser.type'); - const maxAttempts = config.get('xpack.reporting.capture.maxAttempts'); - const exportTypesRegistry = server.plugins.reporting.exportTypesRegistry; + const captureConfig: CaptureConfig = config.get('xpack.reporting.capture'); + const browserType = captureConfig.browser.type; + const maxAttempts = captureConfig.maxAttempts; + const queueConfig: QueueConfig = config.get('xpack.reporting.queue'); + const { exportTypesRegistry, queue: jobQueue } = server.plugins.reporting!; return async function enqueueJob( parentLogger: Logger, @@ -32,7 +38,7 @@ function enqueueJobFn(server: KbnServer) { jobParams: object, user: string, headers: ConditionalHeaders, - request: Request + request: RequestFacade ) { const logger = parentLogger.clone(['queue-job']); const exportType = exportTypesRegistry.getById(exportTypeId); diff --git a/x-pack/legacy/plugins/reporting/server/lib/export_types_registry.ts b/x-pack/legacy/plugins/reporting/server/lib/export_types_registry.ts index 21f954fe739d7..35c97f1db10dc 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/export_types_registry.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/export_types_registry.ts @@ -6,7 +6,7 @@ import { resolve as pathResolve } from 'path'; import glob from 'glob'; -import { KbnServer } from '../../types'; +import { ServerFacade } from '../../types'; import { PLUGIN_ID } from '../../common/constants'; import { oncePerServer } from './once_per_server'; import { LevelLogger } from './level_logger'; @@ -26,7 +26,7 @@ function scan(pattern: string) { } const pattern = pathResolve(__dirname, '../../export_types/*/server/index.[jt]s'); -async function exportTypesRegistryFn(server: KbnServer) { +async function exportTypesRegistryFn(server: ServerFacade) { const logger = LevelLogger.createForServer(server, [PLUGIN_ID, 'exportTypes']); const exportTypesRegistry = new ExportTypesRegistry(); const files: string[] = (await scan(pattern)) as string[]; diff --git a/x-pack/legacy/plugins/reporting/server/lib/once_per_server.ts b/x-pack/legacy/plugins/reporting/server/lib/once_per_server.ts index d4fa35908917d..d73a5b73fecd0 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/once_per_server.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/once_per_server.ts @@ -5,10 +5,10 @@ */ import { memoize, MemoizedFunction } from 'lodash'; -import { KbnServer } from '../../types'; +import { ServerFacade } from '../../types'; -type ServerFn = (server: KbnServer) => any; -type Memo = ((server: KbnServer) => any) & MemoizedFunction; +type ServerFn = (server: ServerFacade) => any; +type Memo = ((server: ServerFacade) => any) & MemoizedFunction; /** * allow this function to be called multiple times, but @@ -22,7 +22,7 @@ type Memo = ((server: KbnServer) => any) & MemoizedFunction; * @return {any} */ export function oncePerServer(fn: ServerFn) { - const memoized: Memo = memoize(function(server: KbnServer) { + const memoized: Memo = memoize(function(server: ServerFacade) { if (arguments.length !== 1) { throw new TypeError('This function expects to be called with a single argument'); } diff --git a/x-pack/legacy/plugins/reporting/server/lib/validate/index.ts b/x-pack/legacy/plugins/reporting/server/lib/validate/index.ts index f785fac58d486..e0382c0205345 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/validate/index.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/validate/index.ts @@ -4,18 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ConfigObject, KbnServer, Logger } from '../../../types'; +import { ServerFacade, Logger } from '../../../types'; import { validateBrowser } from './validate_browser'; import { validateConfig } from './validate_config'; import { validateMaxContentLength } from './validate_max_content_length'; -export async function runValidations( - server: KbnServer, - config: ConfigObject, - logger: Logger, - browserFactory: any -) { +export async function runValidations(server: ServerFacade, logger: Logger, browserFactory: any) { try { + const config = server.config(); await Promise.all([ validateBrowser(server, browserFactory, logger), validateConfig(config, logger), diff --git a/x-pack/legacy/plugins/reporting/server/lib/validate/validate_browser.ts b/x-pack/legacy/plugins/reporting/server/lib/validate/validate_browser.ts index 7d011e6c0886e..031709c85284c 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/validate/validate_browser.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/validate/validate_browser.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ import { Browser } from 'puppeteer'; -import { KbnServer, Logger } from '../../../types'; -import { CHROMIUM } from '../../browsers/browser_types'; +import { BROWSER_TYPE } from '../../../common/constants'; +import { ServerFacade, Logger } from '../../../types'; import { HeadlessChromiumDriverFactory } from '../../browsers/chromium/driver_factory'; /* @@ -13,11 +13,11 @@ import { HeadlessChromiumDriverFactory } from '../../browsers/chromium/driver_fa * to the locally running Kibana instance. */ export const validateBrowser = async ( - server: KbnServer, + server: ServerFacade, browserFactory: HeadlessChromiumDriverFactory, logger: Logger ) => { - if (browserFactory.type === CHROMIUM) { + if (browserFactory.type === BROWSER_TYPE) { return browserFactory .test({ viewport: { width: 800, height: 600 } }, logger) .then((browser: Browser | null) => { diff --git a/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts b/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts index 9dc1e9d12e308..9c7e0a5f27786 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generate_from_jobparams.ts @@ -6,28 +6,33 @@ import boom from 'boom'; import Joi from 'joi'; -import { Request, ResponseToolkit } from 'hapi'; import rison from 'rison-node'; import { API_BASE_URL } from '../../common/constants'; -import { KbnServer } from '../../types'; -import { getRouteConfigFactoryReportingPre } from './lib/route_config_factories'; +import { ServerFacade, RequestFacade, ReportingResponseToolkit } from '../../types'; +import { + getRouteConfigFactoryReportingPre, + GetRouteConfigFactoryFn, + RouteConfigFactory, +} from './lib/route_config_factories'; import { HandlerErrorFunction, HandlerFunction } from './types'; const BASE_GENERATE = `${API_BASE_URL}/generate`; export function registerGenerateFromJobParams( - server: KbnServer, + server: ServerFacade, handler: HandlerFunction, handleError: HandlerErrorFunction ) { - const getRouteConfig = getRouteConfigFactoryReportingPre(server); + const getRouteConfig = () => { + const getOriginalRouteConfig: GetRouteConfigFactoryFn = getRouteConfigFactoryReportingPre( + server + ); + const routeConfigFactory: RouteConfigFactory = getOriginalRouteConfig( + ({ params: { exportType } }) => exportType + ); - // generate report - server.route({ - path: `${BASE_GENERATE}/{exportType}`, - method: 'POST', - config: { - ...getRouteConfig(request => request.params.exportType), + return { + ...routeConfigFactory, validate: { params: Joi.object({ exportType: Joi.string().required(), @@ -41,8 +46,15 @@ export function registerGenerateFromJobParams( jobParams: Joi.string().default(null), }).default(), }, - }, - handler: async (request: Request, h: ResponseToolkit) => { + }; + }; + + // generate report + server.route({ + path: `${BASE_GENERATE}/{exportType}`, + method: 'POST', + options: getRouteConfig(), + handler: async (request: RequestFacade, h: ReportingResponseToolkit) => { let jobParamsRison: string | null; if (request.payload) { @@ -73,11 +85,10 @@ export function registerGenerateFromJobParams( }, }); - // show error about GET method to user + // Get route to generation endpoint: show error about GET method to user server.route({ path: `${BASE_GENERATE}/{p*}`, method: 'GET', - config: getRouteConfig(), handler: () => { const err = boom.methodNotAllowed('GET is not allowed'); err.output.headers.allow = 'POST'; diff --git a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject.ts b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject.ts index baadbc99e82f7..ec0a56f8de442 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject.ts @@ -4,41 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Request, ResponseToolkit } from 'hapi'; import { get } from 'lodash'; - import { API_BASE_GENERATE_V1, CSV_FROM_SAVEDOBJECT_JOB_TYPE } from '../../common/constants'; -import { KbnServer } from '../../types'; +import { ServerFacade, RequestFacade, ReportingResponseToolkit } from '../../types'; import { HandlerErrorFunction, HandlerFunction, QueuedJobPayload } from './types'; -import { getRouteOptions } from './lib/route_config_factories'; +import { getRouteOptionsCsv } from './lib/route_config_factories'; import { getJobParamsFromRequest } from '../../export_types/csv_from_savedobject/server/lib/get_job_params_from_request'; -/* - * 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 - */ -const getJobFromRouteHandler = async ( - handleRoute: HandlerFunction, - handleRouteError: HandlerErrorFunction, - request: Request, - h: ResponseToolkit -): Promise => { - let result: QueuedJobPayload; - try { - const jobParams = getJobParamsFromRequest(request, { isImmediate: false }); - result = await handleRoute(CSV_FROM_SAVEDOBJECT_JOB_TYPE, jobParams, request, h); - } catch (err) { - throw handleRouteError(CSV_FROM_SAVEDOBJECT_JOB_TYPE, err); - } - - if (get(result, 'source.job') == null) { - throw new Error(`The Export handler is expected to return a result with job info! ${result}`); - } - - return result; -}; - /* * This function registers API Endpoints for queuing Reporting jobs. The API inputs are: * - saved object type and ID @@ -49,18 +21,38 @@ const getJobFromRouteHandler = async ( * - local (transient) changes the user made to the saved object */ export function registerGenerateCsvFromSavedObject( - server: KbnServer, + server: ServerFacade, handleRoute: HandlerFunction, handleRouteError: HandlerErrorFunction ) { - const routeOptions = getRouteOptions(server); + const routeOptions = getRouteOptionsCsv(server); server.route({ path: `${API_BASE_GENERATE_V1}/csv/saved-object/{savedObjectType}:{savedObjectId}`, method: 'POST', options: routeOptions, - handler: async (request: Request, h: ResponseToolkit) => { - return getJobFromRouteHandler(handleRoute, handleRouteError, request, h); + handler: async (request: RequestFacade, h: ReportingResponseToolkit) => { + /* + * 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(request, { isImmediate: false }); + result = await handleRoute(CSV_FROM_SAVEDOBJECT_JOB_TYPE, jobParams, request, h); + } catch (err) { + throw handleRouteError(CSV_FROM_SAVEDOBJECT_JOB_TYPE, err); + } + + if (get(result, 'source.job') == null) { + throw new Error( + `The Export handler is expected to return a result with job info! ${result}` + ); + } + + return result; }, }); } diff --git a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts index 8321cf16820c0..557f7c3702038 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generate_from_savedobject_immediate.ts @@ -4,24 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Request, ResponseObject, ResponseToolkit } from 'hapi'; - import { API_BASE_GENERATE_V1 } from '../../common/constants'; import { createJobFactory, executeJobFactory } from '../../export_types/csv_from_savedobject'; import { - KbnServer, + ServerFacade, + RequestFacade, + ResponseFacade, + ReportingResponseToolkit, Logger, JobDocPayload, JobIDForImmediate, JobDocOutputExecuted, } from '../../types'; -import { getRouteOptions } from './lib/route_config_factories'; +import { getRouteOptionsCsv } from './lib/route_config_factories'; import { getJobParamsFromRequest } from '../../export_types/csv_from_savedobject/server/lib/get_job_params_from_request'; -interface KibanaResponse extends ResponseObject { - isBoom: boolean; -} - /* * This function registers API Endpoints for immediate Reporting jobs. The API inputs are: * - saved object type and ID @@ -32,10 +29,10 @@ interface KibanaResponse extends ResponseObject { * - local (transient) changes the user made to the saved object */ export function registerGenerateCsvFromSavedObjectImmediate( - server: KbnServer, + server: ServerFacade, parentLogger: Logger ) { - const routeOptions = getRouteOptions(server); + const routeOptions = getRouteOptionsCsv(server); /* * CSV export with the `immediate` option does not queue a job with Reporting's ESQueue to run the job async. Instead, this does: @@ -46,7 +43,7 @@ export function registerGenerateCsvFromSavedObjectImmediate( path: `${API_BASE_GENERATE_V1}/immediate/csv/saved-object/{savedObjectType}:{savedObjectId}`, method: 'POST', options: routeOptions, - handler: async (request: Request, h: ResponseToolkit) => { + handler: async (request: RequestFacade, h: ReportingResponseToolkit) => { const logger = parentLogger.clone(['savedobject-csv']); const jobParams = getJobParamsFromRequest(request, { isImmediate: true }); const createJobFn = createJobFactory(server); @@ -78,7 +75,7 @@ export function registerGenerateCsvFromSavedObjectImmediate( .type(jobOutputContentType); // Set header for buffer download, not streaming - const { isBoom } = response as KibanaResponse; + const { isBoom } = response as ResponseFacade; if (isBoom == null) { response.header('accept-ranges', 'none'); } diff --git a/x-pack/legacy/plugins/reporting/server/routes/index.ts b/x-pack/legacy/plugins/reporting/server/routes/index.ts index cbfd096e7100b..118d6b3d43c17 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/index.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/index.ts @@ -5,9 +5,8 @@ */ import boom from 'boom'; -import { Request, ResponseToolkit } from 'hapi'; import { API_BASE_URL } from '../../common/constants'; -import { KbnServer, Logger } from '../../types'; +import { ServerFacade, RequestFacade, ReportingResponseToolkit, Logger } from '../../types'; import { enqueueJobFactory } from '../lib/enqueue_job'; import { registerGenerateFromJobParams } from './generate_from_jobparams'; import { registerGenerateCsvFromSavedObject } from './generate_from_savedobject'; @@ -15,9 +14,10 @@ import { registerGenerateCsvFromSavedObjectImmediate } from './generate_from_sav import { registerJobs } from './jobs'; import { registerLegacy } from './legacy'; -export function registerRoutes(server: KbnServer, logger: Logger) { +export function registerRoutes(server: ServerFacade, logger: Logger) { const config = server.config(); const DOWNLOAD_BASE_URL = config.get('server.basePath') + `${API_BASE_URL}/jobs/download`; + // @ts-ignore const { errors: esErrors } = server.plugins.elasticsearch.getCluster('admin'); const enqueueJob = enqueueJobFactory(server); @@ -27,8 +27,8 @@ export function registerRoutes(server: KbnServer, logger: Logger) { async function handler( exportTypeId: string, jobParams: any, - request: Request, - h: ResponseToolkit + request: RequestFacade, + h: ReportingResponseToolkit ) { // @ts-ignore const user = request.pre.user; diff --git a/x-pack/legacy/plugins/reporting/server/routes/jobs.ts b/x-pack/legacy/plugins/reporting/server/routes/jobs.ts index 1acc40fd0a55b..58a212aa0d7fb 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/jobs.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/jobs.ts @@ -5,10 +5,8 @@ */ import boom from 'boom'; -import { RequestQuery } from 'hapi'; -import { Request, ResponseToolkit } from 'hapi'; import { API_BASE_URL } from '../../common/constants'; -import { JobDoc, KbnServer } from '../../types'; +import { JobDoc, ServerFacade, RequestFacade, ReportingResponseToolkit } from '../../types'; // @ts-ignore import { jobsQueryFactory } from '../lib/jobs_query'; // @ts-ignore @@ -20,13 +18,7 @@ import { const MAIN_ENTRY = `${API_BASE_URL}/jobs`; -type ListQuery = RequestQuery & { - page: string; - size: string; - ids?: string; // optional field forbids us from extending RequestQuery -}; - -export function registerJobs(server: KbnServer) { +export function registerJobs(server: ServerFacade) { const jobsQuery = jobsQueryFactory(server); const getRouteConfig = getRouteConfigFactoryManagementPre(server); const getRouteConfigDownload = getRouteConfigFactoryDownloadPre(server); @@ -35,9 +27,9 @@ export function registerJobs(server: KbnServer) { server.route({ path: `${MAIN_ENTRY}/list`, method: 'GET', - config: getRouteConfig(), - handler: (request: Request) => { - const { page: queryPage, size: querySize, ids: queryIds } = request.query as ListQuery; + options: getRouteConfig(), + handler: (request: RequestFacade) => { + const { page: queryPage, size: querySize, ids: queryIds } = request.query; const page = parseInt(queryPage, 10) || 0; const size = Math.min(100, parseInt(querySize, 10) || 10); const jobIds = queryIds ? queryIds.split(',') : null; @@ -57,8 +49,8 @@ export function registerJobs(server: KbnServer) { server.route({ path: `${MAIN_ENTRY}/count`, method: 'GET', - config: getRouteConfig(), - handler: (request: Request) => { + options: getRouteConfig(), + handler: (request: RequestFacade) => { const results = jobsQuery.count(request.pre.management.jobTypes, request.pre.user); return results; }, @@ -68,8 +60,8 @@ export function registerJobs(server: KbnServer) { server.route({ path: `${MAIN_ENTRY}/output/{docId}`, method: 'GET', - config: getRouteConfig(), - handler: (request: Request) => { + options: getRouteConfig(), + handler: (request: RequestFacade) => { const { docId } = request.params; return jobsQuery.get(request.pre.user, docId, { includeContent: true }).then( @@ -94,8 +86,8 @@ export function registerJobs(server: KbnServer) { server.route({ path: `${MAIN_ENTRY}/info/{docId}`, method: 'GET', - config: getRouteConfig(), - handler: (request: Request) => { + options: getRouteConfig(), + handler: (request: RequestFacade) => { const { docId } = request.params; return jobsQuery.get(request.pre.user, docId).then( @@ -127,8 +119,8 @@ export function registerJobs(server: KbnServer) { server.route({ path: `${MAIN_ENTRY}/download/{docId}`, method: 'GET', - config: getRouteConfigDownload(), - handler: async (request: Request, h: ResponseToolkit) => { + options: getRouteConfigDownload(), + handler: async (request: RequestFacade, h: ReportingResponseToolkit) => { const { docId } = request.params; let response = await jobResponseHandler( diff --git a/x-pack/legacy/plugins/reporting/server/routes/legacy.ts b/x-pack/legacy/plugins/reporting/server/routes/legacy.ts index 24caf8003b7d7..011ac4a02bbf9 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/legacy.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/legacy.ts @@ -4,31 +4,34 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Request, ResponseToolkit } from 'hapi'; import querystring from 'querystring'; import { API_BASE_URL } from '../../common/constants'; -import { KbnServer } from '../../types'; -import { getRouteConfigFactoryReportingPre } from './lib/route_config_factories'; +import { ServerFacade, RequestFacade, ReportingResponseToolkit } from '../../types'; +import { + getRouteConfigFactoryReportingPre, + GetRouteConfigFactoryFn, +} from './lib/route_config_factories'; import { HandlerErrorFunction, HandlerFunction } from './types'; -const getStaticFeatureConfig = (getRouteConfig: any, featureId: any) => +const getStaticFeatureConfig = (getRouteConfig: GetRouteConfigFactoryFn, featureId: string) => getRouteConfig(() => featureId); + const BASE_GENERATE = `${API_BASE_URL}/generate`; export function registerLegacy( - server: KbnServer, + server: ServerFacade, handler: HandlerFunction, handleError: HandlerErrorFunction ) { const getRouteConfig = getRouteConfigFactoryReportingPre(server); - function createLegacyPdfRoute({ path, objectType }: { path: string; objectType: any }) { + function createLegacyPdfRoute({ path, objectType }: { path: string; objectType: string }) { const exportTypeId = 'printablePdf'; server.route({ path, method: 'POST', - config: getStaticFeatureConfig(getRouteConfig, exportTypeId), - handler: async (request: Request, h: ResponseToolkit) => { + options: getStaticFeatureConfig(getRouteConfig, exportTypeId), + handler: async (request: RequestFacade, h: ReportingResponseToolkit) => { const message = `The following URL is deprecated and will stop working in the next major version: ${request.url.path}`; server.log(['warning', 'reporting', 'deprecation'], message); diff --git a/x-pack/legacy/plugins/reporting/server/routes/lib/route_config_factories.ts b/x-pack/legacy/plugins/reporting/server/routes/lib/route_config_factories.ts index e09db6e536fee..29ded68d403c5 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/lib/route_config_factories.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/lib/route_config_factories.ts @@ -4,18 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Request } from 'hapi'; import Joi from 'joi'; -import { KbnServer } from '../../../types'; +import { CSV_FROM_SAVEDOBJECT_JOB_TYPE } from '../../../common/constants'; +import { ServerFacade, RequestFacade } from '../../../types'; // @ts-ignore import { authorizedUserPreRoutingFactory } from './authorized_user_pre_routing'; // @ts-ignore import { reportingFeaturePreRoutingFactory } from './reporting_feature_pre_routing'; -import { CSV_FROM_SAVEDOBJECT_JOB_TYPE } from '../../../common/constants'; const API_TAG = 'api'; -interface RouteConfigFactory { +export interface RouteConfigFactory { tags?: string[]; pre: any[]; response?: { @@ -23,10 +22,14 @@ interface RouteConfigFactory { }; } -type GetFeatureFunction = (request: Request) => any; +type GetFeatureFunction = (request: RequestFacade) => any; type PreRoutingFunction = (getFeatureId?: GetFeatureFunction) => any; -export function getRouteConfigFactoryReportingPre(server: KbnServer) { +export type GetRouteConfigFactoryFn = ( + getFeatureId?: GetFeatureFunction | undefined +) => RouteConfigFactory; + +export function getRouteConfigFactoryReportingPre(server: ServerFacade): GetRouteConfigFactoryFn { const authorizedUserPreRouting: PreRoutingFunction = authorizedUserPreRoutingFactory(server); const reportingFeaturePreRouting: PreRoutingFunction = reportingFeaturePreRoutingFactory(server); @@ -43,7 +46,7 @@ export function getRouteConfigFactoryReportingPre(server: KbnServer) { }; } -export function getRouteOptions(server: KbnServer) { +export function getRouteOptionsCsv(server: ServerFacade) { const getRouteConfig = getRouteConfigFactoryReportingPre(server); return { ...getRouteConfig(() => CSV_FROM_SAVEDOBJECT_JOB_TYPE), @@ -64,7 +67,7 @@ export function getRouteOptions(server: KbnServer) { }; } -export function getRouteConfigFactoryManagementPre(server: KbnServer) { +export function getRouteConfigFactoryManagementPre(server: ServerFacade): GetRouteConfigFactoryFn { const authorizedUserPreRouting = authorizedUserPreRoutingFactory(server); const reportingFeaturePreRouting = reportingFeaturePreRoutingFactory(server); const managementPreRouting = reportingFeaturePreRouting(() => 'management'); @@ -84,7 +87,7 @@ export function getRouteConfigFactoryManagementPre(server: KbnServer) { // TOC at the end of the PDF, but it's sending multiple cookies and causing our auth to fail with a 401. // Additionally, the range-request doesn't alleviate any performance issues on the server as the entire // download is loaded into memory. -export function getRouteConfigFactoryDownloadPre(server: KbnServer) { +export function getRouteConfigFactoryDownloadPre(server: ServerFacade): GetRouteConfigFactoryFn { const getManagementRouteConfig = getRouteConfigFactoryManagementPre(server); return (): RouteConfigFactory => ({ ...getManagementRouteConfig(), diff --git a/x-pack/legacy/plugins/reporting/server/routes/types.d.ts b/x-pack/legacy/plugins/reporting/server/routes/types.d.ts index 6004811d8a2b4..0b6a9708c7d1a 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/types.d.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/types.d.ts @@ -4,14 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Request, ResponseToolkit } from 'hapi'; -import { JobDocPayload } from '../../types'; +import { RequestFacade, ReportingResponseToolkit, JobDocPayload } from '../../types'; export type HandlerFunction = ( exportType: any, jobParams: any, - request: Request, - h: ResponseToolkit + request: RequestFacade, + h: ReportingResponseToolkit ) => any; export type HandlerErrorFunction = (exportType: any, err: Error) => any; diff --git a/x-pack/legacy/plugins/reporting/test_helpers/create_mock_server.ts b/x-pack/legacy/plugins/reporting/test_helpers/create_mock_server.ts index 63614a18e829b..226355f5edc61 100644 --- a/x-pack/legacy/plugins/reporting/test_helpers/create_mock_server.ts +++ b/x-pack/legacy/plugins/reporting/test_helpers/create_mock_server.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ import { memoize } from 'lodash'; -import { KbnServer } from '../types'; +import { ServerFacade } from '../types'; -export const createMockServer = ({ settings = {} }: any): KbnServer => { +export const createMockServer = ({ settings = {} }: any): ServerFacade => { const mockServer = { expose: () => { ' '; @@ -41,5 +41,5 @@ export const createMockServer = ({ settings = {} }: any): KbnServer => { return key in settings ? settings[key] : defaultSettings[key]; }); - return (mockServer as unknown) as KbnServer; + return (mockServer as unknown) as ServerFacade; }; diff --git a/x-pack/legacy/plugins/reporting/types.d.ts b/x-pack/legacy/plugins/reporting/types.d.ts index 19c08e59400af..731d0f084a718 100644 --- a/x-pack/legacy/plugins/reporting/types.d.ts +++ b/x-pack/legacy/plugins/reporting/types.d.ts @@ -4,14 +4,26 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Request } from 'hapi'; +import { ResponseObject } from 'hapi'; +import { EventEmitter } from 'events'; +import { Legacy } from 'kibana'; +import { XPackMainPlugin } from '../xpack_main/xpack_main'; +import { ElasticsearchPlugin } from '../../../../src/legacy/core_plugins/elasticsearch'; +import { HeadlessChromiumDriverFactory } from './server/browsers/chromium/driver_factory'; +import { BrowserType } from './server/browsers/types'; -interface UiSettings { - get: (value: string) => string; -} - -type SavedObjectClient = any; +type Job = EventEmitter & { id: string }; +export interface ReportingPlugin { + queue: { + addJob: (type: string, payload: object, options: object) => Job; + }; + exportTypesRegistry: { + getById: (id: string) => ExportTypeDefinition; + getAll: () => ExportTypeDefinition[]; + }; + browserDriverFactory: HeadlessChromiumDriverFactory; +} export interface NetworkPolicyRule { allow: boolean; protocol: string; @@ -23,30 +35,61 @@ export interface NetworkPolicy { rules: NetworkPolicyRule[]; } -// these types shoud be in core kibana and are only here temporarily -export interface KbnServer { - info: { protocol: string }; - config: () => ConfigObject; - expose: () => void; - plugins: Record; - route: any; - log: any; - fieldFormatServiceFactory: (uiConfig: any) => any; - savedObjects: { - getScopedSavedObjectsClient: (fakeRequest: { - headers: object; - getBasePath: () => string; - }) => SavedObjectClient; +// Tracks which parts of the legacy plugin system are being used +export type ServerFacade = Legacy.Server & { + plugins: { + reporting?: ReportingPlugin; + xpack_main?: XPackMainPlugin & { + status?: any; + }; }; - uiSettingsServiceFactory: ({ - savedObjectsClient, - }: { - savedObjectsClient: SavedObjectClient; - }) => UiSettings; +}; + +interface ListQuery { + page: string; + size: string; + ids?: string; // optional field forbids us from extending RequestQuery +} +interface GenerateQuery { + jobParams: string; +} +interface GenerateExportTypePayload { + jobParams: string; +} +interface DownloadParams { + docId: string; } -export interface ConfigObject { - get: (path?: string) => any; +// Tracks which parts of the legacy plugin system are being used +interface ReportingRequest { + query: ListQuery & GenerateQuery; + params: DownloadParams; + payload: GenerateExportTypePayload; + pre: { + management: { + jobTypes: any; + }; + user: any; + }; +} + +export type RequestFacade = ReportingRequest & Legacy.Request; + +export type ResponseFacade = ResponseObject & { + isBoom: boolean; +}; + +export type ReportingResponseToolkit = Legacy.ResponseToolkit; + +export interface CaptureConfig { + browser: { + type: BrowserType; + autoDownload: boolean; + chromium: BrowserConfig; + }; + maxAttempts: number; + networkPolicy: NetworkPolicy; + loadDelay: number; } export interface BrowserConfig { @@ -61,6 +104,19 @@ export interface BrowserConfig { }; } +export interface QueueConfig { + indexInterval: string; + pollEnabled: boolean; + pollInterval: number; + pollIntervalErrorMultiplier: number; + timeout: number; +} + +export interface ScrollConfig { + duration: string; + size: number; +} + export interface ElementPosition { boundingClientRect: { // modern browsers support x/y, but older ones don't @@ -152,7 +208,7 @@ export interface ESQueueWorker { export type ESQueueCreateJobFn = ( jobParams: object, headers: ConditionalHeaders, - request: Request + request: RequestFacade ) => Promise; export type ESQueueWorkerExecuteFn = (jobId: string, job: JobDoc, cancellationToken: any) => void; @@ -161,7 +217,7 @@ export type JobIDForImmediate = null; export type ImmediateExecuteFn = ( jobId: JobIDForImmediate, jobDocPayload: JobDocPayload, - request: Request + request: RequestFacade ) => Promise; export interface ESQueueWorkerOptions { @@ -179,9 +235,9 @@ export interface ESQueueInstance { ) => ESQueueWorker; } -export type CreateJobFactory = (server: KbnServer) => ESQueueCreateJobFn; -export type ExecuteJobFactory = (server: KbnServer) => ESQueueWorkerExecuteFn; -export type ExecuteImmediateJobFactory = (server: KbnServer) => ImmediateExecuteFn; +export type CreateJobFactory = (server: ServerFacade) => ESQueueCreateJobFn; +export type ExecuteJobFactory = (server: ServerFacade) => ESQueueWorkerExecuteFn; +export type ExecuteImmediateJobFactory = (server: ServerFacade) => ImmediateExecuteFn; export interface ExportTypeDefinition { id: string; @@ -193,16 +249,11 @@ export interface ExportTypeDefinition { validLicenses: string[]; } -// Note: this seems to be nearly a duplicate of ExportTypeDefinition -export interface ExportType { - jobType: string; - createJobFactory: any; - executeJobFactory: (server: KbnServer) => ESQueueWorkerExecuteFn; -} - export interface ExportTypesRegistry { register: (exportTypeDefinition: ExportTypeDefinition) => void; } +export { CancellationToken } from './common/cancellation_token'; + // Prefer to import this type using: `import { LevelLogger } from 'relative/path/server/lib';` export { LevelLogger as Logger } from './server/lib/level_logger'; diff --git a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/job_create.js b/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/job_create.js index 3e7d5603ca95b..1d7d0c1dd8dea 100644 --- a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/job_create.js +++ b/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/job_create.js @@ -549,7 +549,7 @@ export class JobCreateUi extends Component { - +

-

+

-

+

diff --git a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/step_histogram.js b/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/step_histogram.js index 991280b4e10de..38aac71c86a23 100644 --- a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/step_histogram.js +++ b/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/step_histogram.js @@ -82,12 +82,12 @@ export class StepHistogramUi extends Component { -

+

-

+
@@ -179,12 +179,12 @@ export class StepHistogramUi extends Component { -

+

-

+
)} description={( diff --git a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/step_logistics.js b/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/step_logistics.js index c3cf39bec5fad..75c70ab6ce20a 100644 --- a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/step_logistics.js +++ b/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/step_logistics.js @@ -233,12 +233,12 @@ export class StepLogisticsUi extends Component { -

+

-

+
@@ -276,12 +276,12 @@ export class StepLogisticsUi extends Component { -

+

-

+ } description={ @@ -316,12 +316,12 @@ export class StepLogisticsUi extends Component { -

+

-

+ } description={ @@ -393,12 +393,12 @@ export class StepLogisticsUi extends Component { -

+

-

+ } description={ @@ -415,12 +415,12 @@ export class StepLogisticsUi extends Component { -
+

-

+ } description={ @@ -456,12 +456,12 @@ export class StepLogisticsUi extends Component { -
+

-

+ } description={ diff --git a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/step_metrics.js b/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/step_metrics.js index 7fd4052bd45f1..1ccdcceabe47a 100644 --- a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/step_metrics.js +++ b/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/step_metrics.js @@ -358,12 +358,12 @@ export class StepMetricsUi extends Component { -

+

-

+
diff --git a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/step_review.js b/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/step_review.js index 213a1938432fc..1f3172d8ee848 100644 --- a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/step_review.js +++ b/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/step_review.js @@ -104,13 +104,13 @@ export class StepReviewUi extends Component { return ( -

+

-

+
{this.renderTabs()} diff --git a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/step_terms.js b/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/step_terms.js index d4a07dd312483..6ea8607aa9632 100644 --- a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/step_terms.js +++ b/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/step_terms.js @@ -81,12 +81,12 @@ export class StepTermsUi extends Component { -

+

-

+
diff --git a/x-pack/legacy/plugins/security/public/components/management/change_password_form/change_password_form.tsx b/x-pack/legacy/plugins/security/public/components/management/change_password_form/change_password_form.tsx index 9521cbdc58a78..61c0b77decd56 100644 --- a/x-pack/legacy/plugins/security/public/components/management/change_password_form/change_password_form.tsx +++ b/x-pack/legacy/plugins/security/public/components/management/change_password_form/change_password_form.tsx @@ -314,7 +314,7 @@ export class ChangePasswordForm extends Component { }; private handleChangePasswordFailure = (error: Record) => { - if (error.body && error.body.statusCode === 401) { + if (error.body && error.body.statusCode === 403) { this.setState({ currentPasswordError: true }); } else { toastNotifications.addDanger( diff --git a/x-pack/legacy/plugins/security/public/components/session_expiration_warning/index.ts b/x-pack/legacy/plugins/security/public/components/session_expiration_warning/index.ts deleted file mode 100644 index 0aa0c1f9fb079..0000000000000 --- a/x-pack/legacy/plugins/security/public/components/session_expiration_warning/index.ts +++ /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 { SessionExpirationWarning } from './session_expiration_warning'; diff --git a/x-pack/legacy/plugins/security/public/hacks/on_session_timeout.js b/x-pack/legacy/plugins/security/public/hacks/on_session_timeout.js index 0f1787820a2f2..81b14ee7d8bf4 100644 --- a/x-pack/legacy/plugins/security/public/hacks/on_session_timeout.js +++ b/x-pack/legacy/plugins/security/public/hacks/on_session_timeout.js @@ -4,15 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; import _ from 'lodash'; -import { i18n } from '@kbn/i18n'; import { uiModules } from 'ui/modules'; import { isSystemApiRequest } from 'ui/system_api'; import { Path } from 'plugins/xpack_main/services/path'; -import { toastNotifications } from 'ui/notify'; -import 'plugins/security/services/auto_logout'; -import { SessionExpirationWarning } from '../components/session_expiration_warning'; +import { npSetup } from 'ui/new_platform'; /** * Client session timeout is decreased by this number so that Kibana server @@ -20,65 +16,19 @@ import { SessionExpirationWarning } from '../components/session_expiration_warni * user session up (invalidate access tokens, redirect to logout portal etc.). * @type {number} */ -const SESSION_TIMEOUT_GRACE_PERIOD_MS = 5000; const module = uiModules.get('security', []); module.config(($httpProvider) => { $httpProvider.interceptors.push(( - $timeout, $q, - $injector, - sessionTimeout, - Private, - autoLogout ) => { - function refreshSession() { - // Make a simple request to keep the session alive - $injector.get('es').ping(); - clearNotifications(); - } - const isUnauthenticated = Path.isUnauthenticated(); - const notificationLifetime = 60 * 1000; - const notificationOptions = { - color: 'warning', - text: ( - - ), - title: i18n.translate('xpack.security.hacks.warningTitle', { - defaultMessage: 'Warning' - }), - toastLifeTimeMs: Math.min( - (sessionTimeout - SESSION_TIMEOUT_GRACE_PERIOD_MS), - notificationLifetime - ), - }; - - let pendingNotification; - let activeNotification; - let pendingSessionExpiration; - - function clearNotifications() { - if (pendingNotification) $timeout.cancel(pendingNotification); - if (pendingSessionExpiration) clearTimeout(pendingSessionExpiration); - if (activeNotification) toastNotifications.remove(activeNotification); - } - - function scheduleNotification() { - pendingNotification = $timeout(showNotification, Math.max(sessionTimeout - notificationLifetime, 0)); - } - - function showNotification() { - activeNotification = toastNotifications.add(notificationOptions); - pendingSessionExpiration = setTimeout(() => autoLogout(), notificationOptions.toastLifeTimeMs); - } function interceptorFactory(responseHandler) { return function interceptor(response) { - if (!isUnauthenticated && !isSystemApiRequest(response.config) && sessionTimeout !== null) { - clearNotifications(); - scheduleNotification(); + if (!isUnauthenticated && !isSystemApiRequest(response.config)) { + npSetup.plugins.security.sessionTimeout.extend(); } return responseHandler(response); }; diff --git a/x-pack/legacy/plugins/security/public/views/logged_out/logged_out.tsx b/x-pack/legacy/plugins/security/public/views/logged_out/logged_out.tsx index 3fb902e54fa5c..369b531e8ddf8 100644 --- a/x-pack/legacy/plugins/security/public/views/logged_out/logged_out.tsx +++ b/x-pack/legacy/plugins/security/public/views/logged_out/logged_out.tsx @@ -11,7 +11,6 @@ import { AuthenticationStatePage } from 'plugins/security/components/authenticat import template from 'plugins/security/views/logged_out/logged_out.html'; import React from 'react'; import { render } from 'react-dom'; -import 'ui/autoload/styles'; import chrome from 'ui/chrome'; import { I18nContext } from 'ui/i18n'; diff --git a/x-pack/legacy/plugins/security/public/views/login/login.tsx b/x-pack/legacy/plugins/security/public/views/login/login.tsx index d8a3116740b54..8b452e4c4fdf5 100644 --- a/x-pack/legacy/plugins/security/public/views/login/login.tsx +++ b/x-pack/legacy/plugins/security/public/views/login/login.tsx @@ -12,7 +12,6 @@ import { LoginPage } from 'plugins/security/views/login/components'; import template from 'plugins/security/views/login/login.html'; import React from 'react'; import { render } from 'react-dom'; -import 'ui/autoload/styles'; import chrome from 'ui/chrome'; import { I18nContext } from 'ui/i18n'; import { parse } from 'url'; diff --git a/x-pack/legacy/plugins/security/public/views/overwritten_session/overwritten_session.tsx b/x-pack/legacy/plugins/security/public/views/overwritten_session/overwritten_session.tsx index f3ea175e53ec4..76088443212b2 100644 --- a/x-pack/legacy/plugins/security/public/views/overwritten_session/overwritten_session.tsx +++ b/x-pack/legacy/plugins/security/public/views/overwritten_session/overwritten_session.tsx @@ -8,7 +8,6 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiButton } from '@elastic/eui'; import React from 'react'; import { render } from 'react-dom'; -import 'ui/autoload/styles'; import chrome from 'ui/chrome'; import { I18nContext } from 'ui/i18n'; import { AuthenticatedUser } from '../../../common/model'; diff --git a/x-pack/legacy/plugins/security/server/routes/api/v1/__tests__/users.js b/x-pack/legacy/plugins/security/server/routes/api/v1/__tests__/users.js index 8444efe8790e7..83dfa778f1b50 100644 --- a/x-pack/legacy/plugins/security/server/routes/api/v1/__tests__/users.js +++ b/x-pack/legacy/plugins/security/server/routes/api/v1/__tests__/users.js @@ -81,7 +81,7 @@ describe('User routes', () => { .resolves(AuthenticationResult.succeeded({})); }); - it('returns 401 if old password is wrong.', async () => { + it('returns 403 if old password is wrong.', async () => { loginStub.resolves(AuthenticationResult.failed(new Error('Something went wrong.'))); const response = await changePasswordRoute.handler(request); @@ -89,13 +89,13 @@ describe('User routes', () => { sinon.assert.notCalled(clusterStub.callWithRequest); expect(response.isBoom).to.be(true); expect(response.output.payload).to.eql({ - statusCode: 401, + statusCode: 403, error: 'Unauthorized', message: 'Something went wrong.' }); }); - it('returns 401 if user can authenticate with new password.', async () => { + it(`returns 401 if user can't authenticate with new password.`, async () => { loginStub .withArgs( sinon.match.instanceOf(KibanaRequest), diff --git a/x-pack/legacy/plugins/security/server/routes/api/v1/users.js b/x-pack/legacy/plugins/security/server/routes/api/v1/users.js index 9cb2ad799a211..1d47dc8875348 100644 --- a/x-pack/legacy/plugins/security/server/routes/api/v1/users.js +++ b/x-pack/legacy/plugins/security/server/routes/api/v1/users.js @@ -108,7 +108,7 @@ export function initUsersApi({ authc: { login }, config }, server) { return Boom.unauthorized(authenticationResult.error); } } catch(err) { - return Boom.unauthorized(err); + throw Boom.forbidden(err); } } diff --git a/x-pack/legacy/plugins/siem/common/graphql/shared/schema.gql.ts b/x-pack/legacy/plugins/siem/common/graphql/shared/schema.gql.ts index 937b8771ac89b..d043c1587d3c3 100644 --- a/x-pack/legacy/plugins/siem/common/graphql/shared/schema.gql.ts +++ b/x-pack/legacy/plugins/siem/common/graphql/shared/schema.gql.ts @@ -14,8 +14,6 @@ export const sharedSchema = gql` to: Float! "The beginning of the timerange" from: Float! - "The default browser set time zone" - timezone: String } type CursorType { diff --git a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/drag_drop_context_wrapper.tsx b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/drag_drop_context_wrapper.tsx index 11b604571378b..c513f7a451240 100644 --- a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/drag_drop_context_wrapper.tsx +++ b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/drag_drop_context_wrapper.tsx @@ -5,7 +5,7 @@ */ import { defaultTo, noop } from 'lodash/fp'; -import * as React from 'react'; +import React, { useCallback } from 'react'; import { DragDropContext, DropResult, DragStart } from 'react-beautiful-dnd'; import { connect } from 'react-redux'; import { Dispatch } from 'redux'; @@ -59,22 +59,25 @@ const onDragEndHandler = ({ */ export const DragDropContextWrapperComponent = React.memo( ({ browserFields, children, dataProviders, dispatch }) => { - function onDragEnd(result: DropResult) { - enableScrolling(); - - if (dataProviders != null) { - onDragEndHandler({ - browserFields, - result, - dataProviders, - dispatch, - }); - } - - if (!draggableIsField(result)) { - document.body.classList.remove(IS_DRAGGING_CLASS_NAME); - } - } + const onDragEnd = useCallback( + (result: DropResult) => { + enableScrolling(); + + if (dataProviders != null) { + onDragEndHandler({ + browserFields, + result, + dataProviders, + dispatch, + }); + } + + if (!draggableIsField(result)) { + document.body.classList.remove(IS_DRAGGING_CLASS_NAME); + } + }, + [browserFields, dataProviders] + ); return ( {children} diff --git a/x-pack/legacy/plugins/siem/public/components/edit_data_provider/index.tsx b/x-pack/legacy/plugins/siem/public/components/edit_data_provider/index.tsx index dc7f2185c26b7..ec6646fc76085 100644 --- a/x-pack/legacy/plugins/siem/public/components/edit_data_provider/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/edit_data_provider/index.tsx @@ -17,7 +17,7 @@ import { EuiSpacer, EuiToolTip, } from '@elastic/eui'; -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, useCallback } from 'react'; import styled, { injectGlobal } from 'styled-components'; import { BrowserFields } from '../../containers/source'; @@ -101,7 +101,7 @@ export const StatefulEditDataProvider = React.memo( const [updatedValue, setUpdatedValue] = useState(value); /** Focuses the Value input if it is visible, falling back to the Save button if it's not */ - function focusInput() { + const focusInput = () => { const elements = document.getElementsByClassName(VALUE_INPUT_CLASS_NAME); if (elements.length > 0) { @@ -113,25 +113,25 @@ export const StatefulEditDataProvider = React.memo( (saveElements[0] as HTMLElement).focus(); } } - } + }; - function onFieldSelected(selectedField: EuiComboBoxOptionProps[]) { + const onFieldSelected = useCallback((selectedField: EuiComboBoxOptionProps[]) => { setUpdatedField(selectedField); focusInput(); - } + }, []); - function onOperatorSelected(operatorSelected: EuiComboBoxOptionProps[]) { + const onOperatorSelected = useCallback((operatorSelected: EuiComboBoxOptionProps[]) => { setUpdatedOperator(operatorSelected); focusInput(); - } + }, []); - function onValueChange(e: React.ChangeEvent) { + const onValueChange = useCallback((e: React.ChangeEvent) => { setUpdatedValue(e.target.value); - } + }, []); - function disableScrolling() { + const disableScrolling = () => { const x = window.pageXOffset !== undefined ? window.pageXOffset @@ -143,11 +143,11 @@ export const StatefulEditDataProvider = React.memo( : (document.documentElement || document.body.parentNode || document.body).scrollTop; window.onscroll = () => window.scrollTo(x, y); - } + }; - function enableScrolling() { + const enableScrolling = () => { window.onscroll = () => noop; - } + }; useEffect(() => { disableScrolling(); diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/__snapshots__/embeddable.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/embeddables/__snapshots__/embeddable.test.tsx.snap new file mode 100644 index 0000000000000..f343316d88c46 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/__snapshots__/embeddable.test.tsx.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Embeddable it renders 1`] = ` + + +

+ Test content +

+
+
+`; diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/__snapshots__/embeddable_header.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/embeddables/__snapshots__/embeddable_header.test.tsx.snap new file mode 100644 index 0000000000000..e88693b292a5d --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/__snapshots__/embeddable_header.test.tsx.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`EmbeddableHeader it renders 1`] = ` + + + +`; diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/__snapshots__/embedded_map.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/embeddables/__snapshots__/embedded_map.test.tsx.snap index 1f23686adca0e..bf0dfd9417875 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/__snapshots__/embedded_map.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/__snapshots__/embedded_map.test.tsx.snap @@ -1,19 +1,34 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`EmbeddedMap renders correctly against snapshot 1`] = ` - + + + + + Map configuration help + + + } > - + - - - + + `; diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/__snapshots__/index_patterns_missing_prompt.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/embeddables/__snapshots__/index_patterns_missing_prompt.test.tsx.snap index 1aa3b087ac0d0..fb896059460b9 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/__snapshots__/index_patterns_missing_prompt.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/__snapshots__/index_patterns_missing_prompt.test.tsx.snap @@ -15,43 +15,41 @@ exports[`IndexPatternsMissingPrompt renders correctly against snapshot 1`] = ` body={

- An ECS compliant Kibana index pattern must be configured to view event data on the map. When using beats, you can run the following setup commands to create the required Kibana index patterns, otherwise you can configure them manually within Kibana settings. + + beats + , + "example": + ./packetbeat setup + , + "setup": + setup + , + } + } + />

- - auditbeat-* - - , - - filebeat-* - - , - - packetbeat-* - - , - - winlogbeat-* - +

} iconType="gisApp" title={

- Required Index Patterns Not Configured + Required index patterns not configured

} titleSize="xs" diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/embeddable.test.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/embeddable.test.tsx new file mode 100644 index 0000000000000..49f5306dc1b60 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/embeddable.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 { shallow } from 'enzyme'; +import toJson from 'enzyme-to-json'; +import React from 'react'; + +import '../../mock/ui_settings'; +import { TestProviders } from '../../mock'; +import { Embeddable } from './embeddable'; + +jest.mock('../../lib/settings/use_kibana_ui_setting'); + +describe('Embeddable', () => { + test('it renders', () => { + const wrapper = shallow( + + +

{'Test content'}

+
+
+ ); + + expect(toJson(wrapper)).toMatchSnapshot(); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/embeddable.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/embeddable.tsx new file mode 100644 index 0000000000000..b9a2d01ee0a70 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/embeddable.tsx @@ -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 { EuiPanel } from '@elastic/eui'; +import React from 'react'; +import styled from 'styled-components'; + +const Panel = styled(EuiPanel)` + overflow: hidden; +`; +Panel.displayName = 'Panel'; + +export interface EmbeddableProps { + children: React.ReactNode; +} + +export const Embeddable = React.memo(({ children }) => ( +
+ {children} +
+)); +Embeddable.displayName = 'Embeddable'; diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/embeddable_header.test.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/embeddable_header.test.tsx new file mode 100644 index 0000000000000..4536da3ba7b97 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/embeddable_header.test.tsx @@ -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. + */ + +import { mount, shallow } from 'enzyme'; +import toJson from 'enzyme-to-json'; +import React from 'react'; + +import '../../mock/ui_settings'; +import { TestProviders } from '../../mock'; +import { EmbeddableHeader } from './embeddable_header'; + +jest.mock('../../lib/settings/use_kibana_ui_setting'); + +describe('EmbeddableHeader', () => { + test('it renders', () => { + const wrapper = shallow( + + + + ); + + expect(toJson(wrapper)).toMatchSnapshot(); + }); + + test('it renders the title', () => { + const wrapper = mount( + + + + ); + + expect( + wrapper + .find('[data-test-subj="header-embeddable-title"]') + .first() + .exists() + ).toBe(true); + }); + + test('it renders supplements when children provided', () => { + const wrapper = mount( + + +

{'Test children'}

+
+
+ ); + + expect( + wrapper + .find('[data-test-subj="header-embeddable-supplements"]') + .first() + .exists() + ).toBe(true); + }); + + test('it DOES NOT render supplements when children not provided', () => { + const wrapper = mount( + + + + ); + + expect( + wrapper + .find('[data-test-subj="header-embeddable-supplements"]') + .first() + .exists() + ).toBe(false); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/embeddable_header.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/embeddable_header.tsx new file mode 100644 index 0000000000000..dbd9e3f763f92 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/embeddable_header.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 { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; +import React from 'react'; +import styled, { css } from 'styled-components'; + +const Header = styled.header.attrs({ + className: 'siemEmbeddable__header', +})` + ${({ theme }) => css` + border-bottom: ${theme.eui.euiBorderThin}; + padding: ${theme.eui.paddingSizes.m}; + `} +`; +Header.displayName = 'Header'; + +export interface EmbeddableHeaderProps { + children?: React.ReactNode; + title: string | React.ReactNode; +} + +export const EmbeddableHeader = React.memo(({ children, title }) => ( +
+ + + +
{title}
+
+
+ + {children && ( + + {children} + + )} +
+
+)); +EmbeddableHeader.displayName = 'EmbeddableHeader'; diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx index 87e38e0fac2e1..1c712f874969c 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx @@ -4,41 +4,74 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiFlexGroup, EuiSpacer } from '@elastic/eui'; +import { EuiLink, EuiText } from '@elastic/eui'; import { Filter } from '@kbn/es-query'; import React, { useEffect, useState } from 'react'; -import { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; import { createPortalNode, InPortal } from 'react-reverse-portal'; import { Query } from 'src/plugins/data/common'; +import styled, { css } from 'styled-components'; +import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links'; +import { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; -import styled from 'styled-components'; -import { start } from '../../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public/legacy'; import { EmbeddablePanel } from '../../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public'; - +import { start } from '../../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public/legacy'; +import { DEFAULT_INDEX_KEY } from '../../../common/constants'; import { getIndexPatternTitleIdMapping } from '../../hooks/api/helpers'; import { useIndexPatterns } from '../../hooks/use_index_patterns'; -import { useKibanaUiSetting } from '../../lib/settings/use_kibana_ui_setting'; -import { DEFAULT_INDEX_KEY } from '../../../common/constants'; -import { useKibanaPlugins } from '../../lib/compose/kibana_plugins'; import { useKibanaCore } from '../../lib/compose/kibana_core'; -import { useStateToaster } from '../toasters'; +import { useKibanaPlugins } from '../../lib/compose/kibana_plugins'; +import { useKibanaUiSetting } from '../../lib/settings/use_kibana_ui_setting'; import { Loader } from '../loader'; +import { useStateToaster } from '../toasters'; +import { Embeddable } from './embeddable'; +import { EmbeddableHeader } from './embeddable_header'; +import { createEmbeddable, displayErrorToast, setupEmbeddablesAPI } from './embedded_map_helpers'; import { IndexPatternsMissingPrompt } from './index_patterns_missing_prompt'; -import { MapEmbeddable, SetQuery } from './types'; +import { MapToolTip } from './map_tool_tip/map_tool_tip'; import * as i18n from './translations'; +import { MapEmbeddable, SetQuery } from './types'; -import { createEmbeddable, displayErrorToast, setupEmbeddablesAPI } from './embedded_map_helpers'; -import { MapToolTip } from './map_tool_tip/map_tool_tip'; +interface EmbeddableMapProps { + maintainRatio?: boolean; +} -const EmbeddableWrapper = styled(EuiFlexGroup)` - position: relative; - height: 400px; - margin: 0; +const EmbeddableMap = styled.div.attrs({ + className: 'siemEmbeddable__map', +})` + ${({ maintainRatio, theme }) => css` + .embPanel { + border: none; + box-shadow: none; + } + + .mapToolbarOverlay__button { + display: none; + } + + ${maintainRatio && + css` + padding-top: calc(3 / 4 * 100%); //4:3 (standard) ratio + position: relative; + + @media only screen and (min-width: ${theme.eui.euiBreakpoints.m}) { + padding-top: calc(9 / 32 * 100%); //32:9 (ultra widescreen) ratio + } - .mapToolbarOverlay__button { - display: none; - } + @media only screen and (min-width: 1441px) and (min-height: 901px) { + padding-top: calc(9 / 21 * 100%); //21:9 (ultrawide) ratio + } + + .embPanel { + bottom: 0; + left: 0; + position: absolute; + right: 0; + top: 0; + } + `} + `} `; +EmbeddableMap.displayName = 'EmbeddableMap'; export interface EmbeddedMapProps { query: Query; @@ -152,11 +185,23 @@ export const EmbeddedMap = React.memo( }, [startDate, endDate]); return isError ? null : ( - <> + + + + + {i18n.EMBEDDABLE_HEADER_HELP} + + + + - + + {embeddable != null ? ( ( ) : ( )} - - - + + ); } ); diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/index_patterns_missing_prompt.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/index_patterns_missing_prompt.tsx index 709725b853dd2..e71398455ee88 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/index_patterns_missing_prompt.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/index_patterns_missing_prompt.tsx @@ -4,69 +4,58 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links'; -import { EuiButton, EuiEmptyPrompt, EuiLink } from '@elastic/eui'; - +import { EuiButton, EuiCode, EuiEmptyPrompt } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; import * as React from 'react'; import chrome from 'ui/chrome'; +import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links'; import * as i18n from './translations'; -interface DocMapping { - beat: string; - docLink: string; -} - -export const IndexPatternsMissingPrompt = React.memo(() => { - const beatsSetupDocMapping: DocMapping[] = [ - { - beat: 'auditbeat', - docLink: `${ELASTIC_WEBSITE_URL}/guide/en/beats/auditbeat/${DOC_LINK_VERSION}/load-kibana-dashboards.html`, - }, - { - beat: 'filebeat', - docLink: `${ELASTIC_WEBSITE_URL}/guide/en/beats/filebeat/${DOC_LINK_VERSION}/load-kibana-dashboards.html`, - }, - { - beat: 'packetbeat', - docLink: `${ELASTIC_WEBSITE_URL}/guide/en/beats/packetbeat/${DOC_LINK_VERSION}/load-kibana-dashboards.html`, - }, - { - beat: 'winlogbeat', - docLink: `${ELASTIC_WEBSITE_URL}/guide/en/beats/winlogbeat/${DOC_LINK_VERSION}/load-kibana-dashboards.html`, - }, - ]; - - return ( - {i18n.ERROR_TITLE}} - titleSize="xs" - body={ - <> -

{i18n.ERROR_DESCRIPTION}

+export const IndexPatternsMissingPrompt = React.memo(() => ( + {i18n.ERROR_TITLE}} + titleSize="xs" + body={ + <> +

+ + {'beats'} + + ), + setup: {'setup'}, + example: {'./packetbeat setup'}, + }} + /> +

-

- {beatsSetupDocMapping - .map(v => ( - - {`${v.beat}-*`} - - )) - .reduce((acc, v) => [acc, ', ', v])} -

- - } - actions={ - - {i18n.ERROR_BUTTON} - - } - /> - ); -}); +

+ +

+ + } + actions={ + + {i18n.ERROR_BUTTON} + + } + /> +)); diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/translations.ts b/x-pack/legacy/plugins/siem/public/components/embeddables/translations.ts index 373bd1d054a43..958619bee19d3 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/translations.ts +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/translations.ts @@ -6,6 +6,20 @@ import { i18n } from '@kbn/i18n'; +export const EMBEDDABLE_HEADER_TITLE = i18n.translate( + 'xpack.siem.components.embeddables.embeddedMap.embeddableHeaderTitle', + { + defaultMessage: 'Network map', + } +); + +export const EMBEDDABLE_HEADER_HELP = i18n.translate( + 'xpack.siem.components.embeddables.embeddedMap.embeddableHeaderHelp', + { + defaultMessage: 'Map configuration help', + } +); + export const MAP_TITLE = i18n.translate( 'xpack.siem.components.embeddables.embeddedMap.embeddablePanelTitle', { @@ -51,15 +65,7 @@ export const ERROR_CREATING_EMBEDDABLE = i18n.translate( export const ERROR_TITLE = i18n.translate( 'xpack.siem.components.embeddables.indexPatternsMissingPrompt.errorTitle', { - defaultMessage: 'Required Index Patterns Not Configured', - } -); - -export const ERROR_DESCRIPTION = i18n.translate( - 'xpack.siem.components.embeddables.indexPatternsMissingPrompt.errorDescription', - { - defaultMessage: - 'An ECS compliant Kibana index pattern must be configured to view event data on the map. When using beats, you can run the following setup commands to create the required Kibana index patterns, otherwise you can configure them manually within Kibana settings.', + defaultMessage: 'Required index patterns not configured', } ); diff --git a/x-pack/legacy/plugins/siem/public/components/event_details/stateful_event_details.tsx b/x-pack/legacy/plugins/siem/public/components/event_details/stateful_event_details.tsx index cb67736829878..c614fd52316bc 100644 --- a/x-pack/legacy/plugins/siem/public/components/event_details/stateful_event_details.tsx +++ b/x-pack/legacy/plugins/siem/public/components/event_details/stateful_event_details.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useState } from 'react'; +import React, { useCallback, useState } from 'react'; import { BrowserFields } from '../../containers/source'; import { ColumnHeader } from '../timeline/body/column_headers/column_header'; @@ -27,6 +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), []); return ( ( data={data} id={id} onUpdateColumns={onUpdateColumns} - onViewSelected={newView => setView(newView)} + onViewSelected={handleSetView} timelineId={timelineId} toggleColumn={toggleColumn} view={view} diff --git a/x-pack/legacy/plugins/siem/public/components/events_viewer/index.tsx b/x-pack/legacy/plugins/siem/public/components/events_viewer/index.tsx index 0ecfb15a67f3b..9483c60dcc552 100644 --- a/x-pack/legacy/plugins/siem/public/components/events_viewer/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/events_viewer/index.tsx @@ -6,7 +6,7 @@ import { Filter } from '@kbn/es-query'; import { isEqual } from 'lodash/fp'; -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, useCallback } from 'react'; import { connect } from 'react-redux'; import { ActionCreator } from 'typescript-fsa'; import { Query } from 'src/plugins/data/common'; @@ -102,32 +102,40 @@ const StatefulEventsViewerComponent = React.memo( }; }, []); - const onChangeItemsPerPage: OnChangeItemsPerPage = itemsChangedPerPage => - updateItemsPerPage({ id, itemsPerPage: itemsChangedPerPage }); - - const toggleColumn = (column: ColumnHeader) => { - const exists = columns.findIndex(c => c.id === column.id) !== -1; + const onChangeItemsPerPage: OnChangeItemsPerPage = useCallback( + itemsChangedPerPage => updateItemsPerPage({ id, itemsPerPage: itemsChangedPerPage }), + [id, updateItemsPerPage] + ); - if (!exists && upsertColumn != null) { - upsertColumn({ - column, - id, - index: 1, - }); - } + const toggleColumn = useCallback( + (column: ColumnHeader) => { + const exists = columns.findIndex(c => c.id === column.id) !== -1; + + if (!exists && upsertColumn != null) { + upsertColumn({ + column, + id, + index: 1, + }); + } + + if (exists && removeColumn != null) { + removeColumn({ + columnId: column.id, + id, + }); + } + }, + [columns, id, upsertColumn, removeColumn] + ); - if (exists && removeColumn != null) { - removeColumn({ - columnId: column.id, - id, - }); - } - }; + const handleOnMouseEnter = useCallback(() => setShowInspect(true), []); + const handleOnMouseLeave = useCallback(() => setShowInspect(false), []); return ( {({ indexPattern, browserFields }) => ( -
setShowInspect(true)} onMouseLeave={() => setShowInspect(false)}> +
- diff --git a/x-pack/legacy/plugins/siem/public/components/field_renderers/field_renderers.test.tsx b/x-pack/legacy/plugins/siem/public/components/field_renderers/field_renderers.test.tsx index 6306dc0d288cf..0fd63bc3f2bf2 100644 --- a/x-pack/legacy/plugins/siem/public/components/field_renderers/field_renderers.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/field_renderers/field_renderers.test.tsx @@ -60,16 +60,14 @@ describe('Field Renderers', () => { describe('#dateRenderer', () => { test('it renders correctly against snapshot', () => { const wrapper = shallow( - {dateRenderer('firstSeen', mockData.complete.source!)} + {dateRenderer(mockData.complete.source!.firstSeen)} ); expect(toJson(wrapper)).toMatchSnapshot(); }); test('it renders emptyTagValue when invalid field provided', () => { - const wrapper = mount( - {dateRenderer('geo.spark_plug', mockData.complete.source!)} - ); + const wrapper = mount({dateRenderer(null)}); expect(wrapper.text()).toEqual(getEmptyValue()); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/field_renderers/field_renderers.tsx b/x-pack/legacy/plugins/siem/public/components/field_renderers/field_renderers.tsx index 1045f9c52e5e1..ffad36c7f9396 100644 --- a/x-pack/legacy/plugins/siem/public/components/field_renderers/field_renderers.tsx +++ b/x-pack/legacy/plugins/siem/public/components/field_renderers/field_renderers.tsx @@ -11,17 +11,11 @@ import React, { Fragment, useState } from 'react'; import { pure } from 'recompose'; import styled from 'styled-components'; -import { - AutonomousSystem, - FlowTarget, - HostEcsFields, - IpOverviewData, - Overview, -} from '../../graphql/types'; +import { AutonomousSystem, FlowTarget, HostEcsFields, IpOverviewData } from '../../graphql/types'; import { escapeDataProviderId } from '../drag_and_drop/helpers'; import { DefaultDraggable } from '../draggables'; import { getEmptyTagValue } from '../empty_value'; -import { FormattedDate } from '../formatted_date'; +import { FormattedRelativePreferenceDate } from '../formatted_date'; import { HostDetailsLink, ReputationLink, VirusTotalLink, WhoIsLink } from '../links'; import { Spacer } from '../page'; import * as i18n from '../page/network/ip_overview/translations'; @@ -58,8 +52,8 @@ export const locationRenderer = (fieldNames: string[], data: IpOverviewData): Re getEmptyTagValue() ); -export const dateRenderer = (fieldName: string, data: Overview): React.ReactElement => ( - +export const dateRenderer = (timestamp?: string | null): React.ReactElement => ( + ); export const autonomousSystemRenderer = ( diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/category_columns.test.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/category_columns.test.tsx index 6406c4609a266..0a8fd9b54f4aa 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/category_columns.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/category_columns.test.tsx @@ -112,7 +112,7 @@ describe('getCategoryColumns', () => { expect( wrapper.find(`.field-browser-category-pane-${selectedCategoryId}-${timelineId}`).first() - ).toHaveStyleRule('font-weight', 'bold'); + ).toHaveStyleRule('font-weight', 'bold', { modifier: '.euiText' }); }); test('it does NOT render an un-selected category with bold text', () => { @@ -135,7 +135,7 @@ describe('getCategoryColumns', () => { expect( wrapper.find(`.field-browser-category-pane-${notTheSelectedCategoryId}-${timelineId}`).first() - ).toHaveStyleRule('font-weight', 'normal'); + ).toHaveStyleRule('font-weight', 'normal', { modifier: '.euiText' }); }); test('it invokes onCategorySelected when a user clicks a category', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/category_columns.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/category_columns.tsx index 2581fba75da1e..1845a0bae88d5 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/category_columns.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/category_columns.tsx @@ -26,7 +26,9 @@ import { LoadingSpinner, getCategoryPaneCategoryClassName, getFieldCount } from import * as i18n from './translations'; const CategoryName = styled.span<{ bold: boolean }>` - font-weight: ${({ bold }) => (bold ? 'bold' : 'normal')}; + .euiText { + font-weight: ${({ bold }) => (bold ? 'bold' : 'normal')}; + } `; CategoryName.displayName = 'CategoryName'; diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/field_browser.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/field_browser.tsx index fb47672512de5..70604f73105f9 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/field_browser.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/field_browser.tsx @@ -5,7 +5,7 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiOutsideClickDetector } from '@elastic/eui'; -import React, { useEffect } from 'react'; +import React, { useEffect, useCallback } from 'react'; import { noop } from 'lodash/fp'; import styled, { css } from 'styled-components'; @@ -122,7 +122,7 @@ export const FieldsBrowser = React.memo( width, }) => { /** Focuses the input that filters the field browser */ - function focusInput() { + const focusInput = () => { const elements = document.getElementsByClassName( getFieldBrowserSearchInputClassName(timelineId) ); @@ -130,22 +130,28 @@ export const FieldsBrowser = React.memo( if (elements.length > 0) { (elements[0] as HTMLElement).focus(); // this cast is required because focus() does not exist on every `Element` returned by `getElementsByClassName` } - } + }; /** Invoked when the user types in the input to filter the field browser */ - function onInputChange(event: React.ChangeEvent) { - onSearchInputChange(event.target.value); - } + const onInputChange = useCallback( + (event: React.ChangeEvent) => { + onSearchInputChange(event.target.value); + }, + [onSearchInputChange] + ); - function selectFieldAndHide(fieldId: string) { - if (onFieldSelected != null) { - onFieldSelected(fieldId); - } + const selectFieldAndHide = useCallback( + (fieldId: string) => { + if (onFieldSelected != null) { + onFieldSelected(fieldId); + } - onHideFieldBrowser(); - } + onHideFieldBrowser(); + }, + [onFieldSelected, onHideFieldBrowser] + ); - function scrollViews() { + const scrollViews = () => { if (selectedCategoryId !== '') { const categoryPaneTitles = document.getElementsByClassName( getCategoryPaneCategoryClassName({ @@ -171,7 +177,7 @@ export const FieldsBrowser = React.memo( } focusInput(); // always re-focus the input to enable additional filtering - } + }; useEffect(() => { scrollViews(); diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/index.test.tsx index 4c9c1fc4147ab..8948f765b8fbc 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/index.test.tsx @@ -14,8 +14,17 @@ import { TestProviders } from '../../mock'; import { FIELD_BROWSER_HEIGHT, FIELD_BROWSER_WIDTH } from './helpers'; import { StatefulFieldsBrowser } from '.'; - +// Suppress warnings about "act" until async/await syntax is supported: https://github.com/facebook/react/issues/14769 +/* eslint-disable no-console */ +const originalError = console.error; describe('StatefulFieldsBrowser', () => { + beforeAll(() => { + console.error = jest.fn(); + }); + + afterAll(() => { + console.error = originalError; + }); const timelineId = 'test'; test('it renders the Fields button, which displays the fields browser on click', () => { @@ -85,6 +94,9 @@ describe('StatefulFieldsBrowser', () => { }); describe('updateSelectedCategoryId', () => { + beforeEach(() => { + jest.useFakeTimers(); + }); test('it updates the selectedCategoryId state, which makes the category bold, when the user clicks a category name in the left hand side of the field browser', () => { const wrapper = mount( @@ -111,10 +123,43 @@ describe('StatefulFieldsBrowser', () => { .simulate('click'); wrapper.update(); - expect( wrapper.find(`.field-browser-category-pane-auditd-${timelineId}`).first() - ).toHaveStyleRule('font-weight', 'bold'); + ).toHaveStyleRule('font-weight', 'bold', { modifier: '.euiText' }); + }); + + test('it updates the selectedCategoryId state according to most fields returned', () => { + const wrapper = mount( + + + + ); + + wrapper + .find('[data-test-subj="show-field-browser"]') + .first() + .simulate('click'); + expect( + wrapper.find(`.field-browser-category-pane-cloud-${timelineId}`).first() + ).toHaveStyleRule('font-weight', 'normal', { modifier: '.euiText' }); + wrapper + .find('[data-test-subj="field-search"]') + .last() + .simulate('change', { target: { value: 'cloud' } }); + + jest.runOnlyPendingTimers(); + wrapper.update(); + expect( + wrapper.find(`.field-browser-category-pane-cloud-${timelineId}`).first() + ).toHaveStyleRule('font-weight', 'bold', { modifier: '.euiText' }); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/index.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/index.tsx index 7d21e1f44d04b..2c8092a3295ad 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/index.tsx @@ -6,7 +6,7 @@ import { EuiButtonEmpty, EuiButtonIcon, EuiToolTip } from '@elastic/eui'; import { noop } from 'lodash/fp'; -import React, { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useRef, useState, useCallback, useMemo } from 'react'; import { connect } from 'react-redux'; import styled from 'styled-components'; import { ActionCreator } from 'typescript-fsa'; @@ -23,7 +23,7 @@ import { FieldBrowserProps } from './types'; const fieldsButtonClassName = 'fields-button'; /** wait this many ms after the user completes typing before applying the filter input */ -const INPUT_TIMEOUT = 250; +export const INPUT_TIMEOUT = 250; const FieldsBrowserButtonContainer = styled.div` position: relative; @@ -82,78 +82,79 @@ export const StatefulFieldsBrowserComponent = React.memo { setShow(!show); - } + }, [show]); /** Invoked when the user types in the filter input */ - function updateFilter(newFilterInput: string) { - setFilterInput(newFilterInput); - setIsSearching(true); - - if (inputTimeoutId.current !== 0) { - clearTimeout(inputTimeoutId.current); // ⚠️ mutation: cancel any previous timers - } - - // ⚠️ mutation: schedule a new timer that will apply the filter when it fires: - inputTimeoutId.current = window.setTimeout(() => { - const newFilteredBrowserFields = filterBrowserFieldsByFieldName({ - browserFields: mergeBrowserFieldsWithDefaultCategory(browserFields), - substring: filterInput, - }); - - setFilteredBrowserFields(newFilteredBrowserFields); - setIsSearching(false); - - const newSelectedCategoryId = - filterInput === '' || Object.keys(newFilteredBrowserFields).length === 0 - ? DEFAULT_CATEGORY_NAME - : Object.keys(newFilteredBrowserFields) - .sort() - .reduce( - (selected, category) => - newFilteredBrowserFields[category].fields != null && - newFilteredBrowserFields[selected].fields != null && - newFilteredBrowserFields[category].fields!.length > - newFilteredBrowserFields[selected].fields!.length - ? category - : selected, - Object.keys(newFilteredBrowserFields)[0] - ); - setSelectedCategoryId(newSelectedCategoryId); - }, INPUT_TIMEOUT); - } + const updateFilter = useCallback( + (newFilterInput: string) => { + setFilterInput(newFilterInput); + setIsSearching(true); + if (inputTimeoutId.current !== 0) { + clearTimeout(inputTimeoutId.current); // ⚠️ mutation: cancel any previous timers + } + // ⚠️ mutation: schedule a new timer that will apply the filter when it fires: + inputTimeoutId.current = window.setTimeout(() => { + const newFilteredBrowserFields = filterBrowserFieldsByFieldName({ + browserFields: mergeBrowserFieldsWithDefaultCategory(browserFields), + substring: newFilterInput, + }); + setFilteredBrowserFields(newFilteredBrowserFields); + setIsSearching(false); + + const newSelectedCategoryId = + newFilterInput === '' || Object.keys(newFilteredBrowserFields).length === 0 + ? DEFAULT_CATEGORY_NAME + : Object.keys(newFilteredBrowserFields) + .sort() + .reduce( + (selected, category) => + newFilteredBrowserFields[category].fields != null && + newFilteredBrowserFields[selected].fields != null && + Object.keys(newFilteredBrowserFields[category].fields!).length > + Object.keys(newFilteredBrowserFields[selected].fields!).length + ? category + : selected, + Object.keys(newFilteredBrowserFields)[0] + ); + setSelectedCategoryId(newSelectedCategoryId); + }, INPUT_TIMEOUT); + }, + [browserFields, filterInput, inputTimeoutId.current] + ); /** * Invoked when the user clicks a category name in the left-hand side of * the field browser */ - function updateSelectedCategoryId(categoryId: string) { + const updateSelectedCategoryId = useCallback((categoryId: string) => { setSelectedCategoryId(categoryId); - } + }, []); /** * Invoked when the user clicks on the context menu to view a category's * columns in the timeline, this function dispatches the action that * causes the timeline display those columns. */ - function updateColumnsAndSelectCategoryId(columns: ColumnHeader[]) { + const updateColumnsAndSelectCategoryId = useCallback((columns: ColumnHeader[]) => { onUpdateColumns(columns); // show the category columns in the timeline - } + }, []); /** Invoked when the field browser should be hidden */ - function hideFieldBrowser() { + const hideFieldBrowser = useCallback(() => { setFilterInput(''); setFilterInput(''); setFilteredBrowserFields(null); setIsSearching(false); setSelectedCategoryId(DEFAULT_CATEGORY_NAME); setShow(false); - } + }, []); // only merge in the default category if the field browser is visible - const browserFieldsWithDefaultCategory = show - ? mergeBrowserFieldsWithDefaultCategory(browserFields) - : {}; + const browserFieldsWithDefaultCategory = useMemo( + () => (show ? mergeBrowserFieldsWithDefaultCategory(browserFields) : {}), + [show, browserFields] + ); return ( <> diff --git a/x-pack/legacy/plugins/siem/public/components/flyout/header/index.tsx b/x-pack/legacy/plugins/siem/public/components/flyout/header/index.tsx index 1d0e03265f5ea..2f4da30672e8c 100644 --- a/x-pack/legacy/plugins/siem/public/components/flyout/header/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/flyout/header/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React, { useCallback } from 'react'; import { connect } from 'react-redux'; import { Dispatch } from 'redux'; import { ActionCreator } from 'typescript-fsa'; @@ -22,7 +22,7 @@ import { import { UpdateNote } from '../../notes/helpers'; import { defaultHeaders } from '../../timeline/body/column_headers/default_headers'; import { Properties } from '../../timeline/properties'; -import { appActions } from '../../../store/app'; +import { appActions, appModel } from '../../../store/app'; import { inputsActions } from '../../../store/inputs'; import { timelineActions } from '../../../store/actions'; import { TimelineModel } from '../../../store/timeline/model'; @@ -36,7 +36,7 @@ interface OwnProps { interface StateReduxProps { description: string; - getNotesByIds: (noteIds: string[]) => Note[]; + notesById: appModel.NotesById; isDataInTimeline: boolean; isDatepickerLocked: boolean; isFavorite: boolean; @@ -75,13 +75,13 @@ const StatefulFlyoutHeader = React.memo( associateNote, createTimeline, description, - getNotesByIds, isFavorite, isDataInTimeline, isDatepickerLocked, title, width = DEFAULT_TIMELINE_WIDTH, noteIds, + notesById, timelineId, toggleLock, updateDescription, @@ -89,27 +89,33 @@ const StatefulFlyoutHeader = React.memo( updateNote, updateTitle, usersViewing, - }) => ( - - ) + }) => { + const getNotesByIds = useCallback( + (noteIdsVar: string[]): Note[] => appSelectors.getNotes(notesById, noteIdsVar), + [notesById] + ); + return ( + + ); + } ); StatefulFlyoutHeader.displayName = 'StatefulFlyoutHeader'; @@ -139,7 +145,7 @@ const makeMapStateToProps = () => { return { description, - getNotesByIds: getNotesByIds(state), + notesById: getNotesByIds(state), history, isDataInTimeline: !isEmpty(dataProviders) || !isEmpty(get('filterQuery.kuery.expression', kqlQuery)), diff --git a/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.tsx b/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.tsx index ceaff289f776c..ba5275ed79aef 100644 --- a/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.tsx @@ -5,7 +5,7 @@ */ import { EuiButtonIcon, EuiFlyout, EuiFlyoutBody, EuiFlyoutHeader, EuiToolTip } from '@elastic/eui'; -import * as React from 'react'; +import React, { useCallback } from 'react'; import { connect } from 'react-redux'; import styled from 'styled-components'; import { ActionCreator } from 'typescript-fsa'; @@ -122,19 +122,22 @@ const FlyoutPaneComponent = React.memo( usersViewing, width, }) => { - const renderFlyout = () => <>; - - const onResize: OnResize = ({ delta, id }) => { - const bodyClientWidthPixels = document.body.clientWidth; - - applyDeltaToWidth({ - bodyClientWidthPixels, - delta, - id, - maxWidthPercent, - minWidthPixels, - }); - }; + const renderFlyout = useCallback(() => <>, []); + + const onResize: OnResize = useCallback( + ({ delta, id }) => { + const bodyClientWidthPixels = document.body.clientWidth; + + applyDeltaToWidth({ + bodyClientWidthPixels, + delta, + id, + maxWidthPercent, + minWidthPixels, + }); + }, + [applyDeltaToWidth, maxWidthPercent, minWidthPixels] + ); return ( ({ @@ -162,4 +162,45 @@ describe('formatted_date', () => { }); }); }); + + describe('FormattedRelativePreferenceDate', () => { + describe('rendering', () => { + test('renders time over an hour correctly against snapshot', () => { + const isoDateString = '2019-02-25T22:27:05.000Z'; + const wrapper = shallow(); + expect(wrapper.find('[data-test-subj="preference-time"]').exists()).toBe(true); + }); + test('renders time under an hour correctly against snapshot', () => { + const timeTwelveMinutesAgo = new Date(new Date().getTime() - 12 * 60 * 1000).toISOString(); + const wrapper = shallow(); + expect(wrapper.find('[data-test-subj="relative-time"]').exists()).toBe(true); + }); + test('renders empty string value correctly', () => { + const wrapper = mount( + + + + ); + expect(wrapper.text()).toBe(getEmptyString()); + }); + + test('renders undefined value correctly', () => { + const wrapper = mount( + + + + ); + expect(wrapper.text()).toBe(getEmptyValue()); + }); + + test('renders null value correctly', () => { + const wrapper = mount( + + + + ); + expect(wrapper.text()).toBe(getEmptyValue()); + }); + }); + }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/formatted_date/index.tsx b/x-pack/legacy/plugins/siem/public/components/formatted_date/index.tsx index 22a3893cf0f98..32c064096fcf9 100644 --- a/x-pack/legacy/plugins/siem/public/components/formatted_date/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/formatted_date/index.tsx @@ -6,6 +6,7 @@ import moment from 'moment-timezone'; import * as React from 'react'; +import { FormattedRelative } from '@kbn/i18n/react'; import { pure } from 'recompose'; import { @@ -62,3 +63,36 @@ export const FormattedDate = pure<{ ); FormattedDate.displayName = 'FormattedDate'; + +/** + * Renders the specified date value according to under/over one hour + * Under an hour = relative format + * Over an hour = in a format determined by the user's preferences, + * with a tooltip that renders: + * - the name of the field + * - a humanized relative date (e.g. 16 minutes ago) + * - a long representation of the date that includes the day of the week (e.g. Thursday, March 21, 2019 6:47pm) + * - the raw date value (e.g. 2019-03-22T00:47:46Z) + */ + +export const FormattedRelativePreferenceDate = ({ value }: { value?: string | number | null }) => { + if (value == null) { + return getOrEmptyTagFromValue(value); + } + const maybeDate = getMaybeDate(value); + if (!maybeDate.isValid()) { + return getOrEmptyTagFromValue(value); + } + const date = maybeDate.toDate(); + return ( + + {moment(date) + .add(1, 'hours') + .isBefore(new Date()) ? ( + + ) : ( + + )} + + ); +}; diff --git a/x-pack/legacy/plugins/siem/public/components/last_event_time/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/last_event_time/index.test.tsx index bcf5e3c1de408..c23a757647a42 100644 --- a/x-pack/legacy/plugins/siem/public/components/last_event_time/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/last_event_time/index.test.tsx @@ -53,8 +53,7 @@ describe('Last Event Time Stat', () => { ); - - expect(wrapper.html()).toBe('Last event: 12 days ago'); + expect(wrapper.html()).toBe('Last event: 12 minutes ago'); }); test('Bad date time string', async () => { mockUseLastEventTimeQuery.mockImplementation(() => ({ diff --git a/x-pack/legacy/plugins/siem/public/components/last_event_time/index.tsx b/x-pack/legacy/plugins/siem/public/components/last_event_time/index.tsx index cb9895bebcf09..2493a1378e944 100644 --- a/x-pack/legacy/plugins/siem/public/components/last_event_time/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/last_event_time/index.tsx @@ -5,18 +5,20 @@ */ import { EuiIcon, EuiLoadingSpinner, EuiToolTip } from '@elastic/eui'; -import { FormattedMessage, FormattedRelative } from '@kbn/i18n/react'; +import { FormattedMessage } from '@kbn/i18n/react'; import React, { memo } from 'react'; import { LastEventIndexKey } from '../../graphql/types'; import { useLastEventTimeQuery } from '../../containers/events/last_event_time'; import { getEmptyTagValue } from '../empty_value'; +import { FormattedRelativePreferenceDate } from '../formatted_date'; export interface LastEventTimeProps { hostName?: string; indexKey: LastEventIndexKey; ip?: string; } + export const LastEventTime = memo(({ hostName, indexKey, ip }) => { const { loading, lastSeen, errorMessage } = useLastEventTimeQuery( indexKey, @@ -37,6 +39,7 @@ export const LastEventTime = memo(({ hostName, indexKey, ip ); } + return ( <> {loading && } @@ -44,15 +47,13 @@ export const LastEventTime = memo(({ hostName, indexKey, ip ? lastSeen : !loading && lastSeen != null && ( - - , - }} - /> - + , + }} + /> )} {!loading && lastSeen == null && getEmptyTagValue()} diff --git a/x-pack/legacy/plugins/siem/public/components/lazy_accordion/index.tsx b/x-pack/legacy/plugins/siem/public/components/lazy_accordion/index.tsx index da2e7334756e4..008813040138f 100644 --- a/x-pack/legacy/plugins/siem/public/components/lazy_accordion/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/lazy_accordion/index.tsx @@ -5,7 +5,7 @@ */ import { EuiAccordion, EuiAccordionProps } from '@elastic/eui'; -import React, { useState } from 'react'; +import React, { useState, useCallback } from 'react'; type Props = Pick> & { forceExpand?: boolean; @@ -42,19 +42,19 @@ export const LazyAccordion = React.memo( renderExpandedContent, }) => { const [expanded, setExpanded] = useState(false); - const onCollapsedClick = () => { + const onCollapsedClick = useCallback(() => { setExpanded(true); if (onExpand != null) { onExpand(); } - }; + }, [onExpand]); - const onExpandedClick = () => { + const onExpandedClick = useCallback(() => { setExpanded(false); if (onCollapse != null) { onCollapse(); } - }; + }, [onCollapse]); return ( <> diff --git a/x-pack/legacy/plugins/siem/public/components/matrix_over_time/index.tsx b/x-pack/legacy/plugins/siem/public/components/matrix_over_time/index.tsx index 2898541a4a3d1..3523723574be6 100644 --- a/x-pack/legacy/plugins/siem/public/components/matrix_over_time/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/matrix_over_time/index.tsx @@ -31,8 +31,9 @@ export interface MatrixOverTimeBasicProps { } export interface MatrixOverTimeProps extends MatrixOverTimeBasicProps { + customChartData?: ChartSeriesData[]; title: string; - subtitle: string; + subtitle?: string; dataKey: string; } @@ -73,6 +74,7 @@ const getBarchartConfigs = (from: number, to: number, onBrushEnd: UpdateDateRang }); export const MatrixOverTimeHistogram = ({ + customChartData, id, loading, data, @@ -91,7 +93,7 @@ export const MatrixOverTimeHistogram = ({ const [darkMode] = useKibanaUiSetting(DEFAULT_DARK_MODE); const [loadingInitial, setLoadingInitial] = useState(false); - const barChartData: ChartSeriesData[] = [ + const barChartData: ChartSeriesData[] = customChartData || [ { key: dataKey, value: data, diff --git a/x-pack/legacy/plugins/siem/public/components/ml/tables/get_anomalies_host_table_columns.tsx b/x-pack/legacy/plugins/siem/public/components/ml/tables/get_anomalies_host_table_columns.tsx index 6650449dd8200..daac4835adb28 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/tables/get_anomalies_host_table_columns.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml/tables/get_anomalies_host_table_columns.tsx @@ -6,7 +6,6 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui'; -import moment from 'moment'; import { Columns } from '../../paginated_table'; import { AnomaliesByHost, Anomaly, NarrowDateRange } from '../types'; import { getRowItemDraggable } from '../../tables/helpers'; @@ -18,10 +17,9 @@ import * as i18n from './translations'; import { getEntries } from '../get_entries'; import { DraggableScore } from '../score/draggable_score'; import { createExplorerLink } from '../links/create_explorer_link'; -import { LocalizedDateTooltip } from '../../localized_date_tooltip'; -import { PreferenceFormattedDate } from '../../formatted_date'; import { HostsType } from '../../../store/hosts/model'; import { escapeDataProviderId } from '../../drag_and_drop/helpers'; +import { FormattedRelativePreferenceDate } from '../../formatted_date'; export const getAnomaliesHostTableColumns = ( startDate: number, @@ -126,11 +124,7 @@ export const getAnomaliesHostTableColumns = ( name: i18n.TIME_STAMP, field: 'anomaly.time', sortable: true, - render: time => ( - - - - ), + render: time => , }, ]; diff --git a/x-pack/legacy/plugins/siem/public/components/ml/tables/get_anomalies_network_table_columns.tsx b/x-pack/legacy/plugins/siem/public/components/ml/tables/get_anomalies_network_table_columns.tsx index 1e1628fb077dd..2113d3b82f52e 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/tables/get_anomalies_network_table_columns.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml/tables/get_anomalies_network_table_columns.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui'; -import moment from 'moment'; + import { Columns } from '../../paginated_table'; import { Anomaly, NarrowDateRange, AnomaliesByNetwork } from '../types'; import { getRowItemDraggable } from '../../tables/helpers'; @@ -18,8 +18,7 @@ import * as i18n from './translations'; import { getEntries } from '../get_entries'; import { DraggableScore } from '../score/draggable_score'; import { createExplorerLink } from '../links/create_explorer_link'; -import { LocalizedDateTooltip } from '../../localized_date_tooltip'; -import { PreferenceFormattedDate } from '../../formatted_date'; +import { FormattedRelativePreferenceDate } from '../../formatted_date'; import { NetworkType } from '../../../store/network/model'; import { escapeDataProviderId } from '../../drag_and_drop/helpers'; @@ -120,11 +119,7 @@ export const getAnomaliesNetworkTableColumns = ( name: i18n.TIME_STAMP, field: 'anomaly.time', sortable: true, - render: time => ( - - - - ), + render: time => , }, ]; diff --git a/x-pack/legacy/plugins/siem/public/components/notes/note_cards/index.tsx b/x-pack/legacy/plugins/siem/public/components/notes/note_cards/index.tsx index aa9415aadeda1..6664660eb6bdc 100644 --- a/x-pack/legacy/plugins/siem/public/components/notes/note_cards/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/notes/note_cards/index.tsx @@ -5,7 +5,7 @@ */ import { EuiFlexGroup, EuiPanel } from '@elastic/eui'; -import React, { useState } from 'react'; +import React, { useState, useCallback } from 'react'; import styled from 'styled-components'; import { Note } from '../../../lib/note'; @@ -66,10 +66,13 @@ export const NoteCards = React.memo( }) => { const [newNote, setNewNote] = useState(''); - const associateNoteAndToggleShow = (noteId: string) => { - associateNote(noteId); - toggleShowAddNote(); - }; + const associateNoteAndToggleShow = useCallback( + (noteId: string) => { + associateNote(noteId); + toggleShowAddNote(); + }, + [associateNote, toggleShowAddNote] + ); return ( diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/index.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/index.tsx index d101d1f4d39f4..c96d25f0d11f0 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/index.tsx @@ -5,7 +5,7 @@ */ import ApolloClient from 'apollo-client'; -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, useCallback } from 'react'; import { connect } from 'react-redux'; import { Dispatch } from 'redux'; @@ -98,9 +98,9 @@ export const StatefulOpenTimelineComponent = React.memo( const [sortField, setSortField] = useState(DEFAULT_SORT_FIELD); /** Invoked when the user presses enters to submit the text in the search input */ - const onQueryChange: OnQueryChange = (query: EuiSearchBarQuery) => { + const onQueryChange: OnQueryChange = useCallback((query: EuiSearchBarQuery) => { setSearch(query.queryText.trim()); - }; + }, []); /** Focuses the input that filters the field browser */ const focusInput = () => { @@ -126,23 +126,26 @@ export const StatefulOpenTimelineComponent = React.memo( // } // }; - const onDeleteOneTimeline: OnDeleteOneTimeline = (timelineIds: string[]) => { - deleteTimelines(timelineIds, { - search, - pageInfo: { - pageIndex: pageIndex + 1, - pageSize, - }, - sort: { - sortField: sortField as SortFieldTimeline, - sortOrder: sortDirection as Direction, - }, - onlyUserFavorite: onlyFavorites, - }); - }; + const onDeleteOneTimeline: OnDeleteOneTimeline = useCallback( + (timelineIds: string[]) => { + deleteTimelines(timelineIds, { + search, + pageInfo: { + pageIndex: pageIndex + 1, + pageSize, + }, + sort: { + sortField: sortField as SortFieldTimeline, + sortOrder: sortDirection as Direction, + }, + onlyUserFavorite: onlyFavorites, + }); + }, + [search, pageIndex, pageSize, sortField, sortDirection, onlyFavorites] + ); /** Invoked when the user clicks the action to delete the selected timelines */ - const onDeleteSelected: OnDeleteSelected = () => { + const onDeleteSelected: OnDeleteSelected = useCallback(() => { deleteTimelines(getSelectedTimelineIds(selectedItems), { search, pageInfo: { @@ -161,79 +164,81 @@ export const StatefulOpenTimelineComponent = React.memo( resetSelectionState(); // TODO: the query must re-execute to show the results of the deletion - }; + }, [selectedItems, search, pageIndex, pageSize, sortField, sortDirection, onlyFavorites]); /** Invoked when the user selects (or de-selects) timelines */ - const onSelectionChange: OnSelectionChange = (newSelectedItems: OpenTimelineResult[]) => { - setSelectedItems(newSelectedItems); // <-- this is NOT passed down as props to the table: https://github.com/elastic/eui/issues/1077 - }; + const onSelectionChange: OnSelectionChange = useCallback( + (newSelectedItems: OpenTimelineResult[]) => { + setSelectedItems(newSelectedItems); // <-- this is NOT passed down as props to the table: https://github.com/elastic/eui/issues/1077 + }, + [] + ); /** Invoked by the EUI table implementation when the user interacts with the table (i.e. to update sorting) */ - const onTableChange: OnTableChange = ({ page, sort }: OnTableChangeParams) => { + const onTableChange: OnTableChange = useCallback(({ page, sort }: OnTableChangeParams) => { const { index, size } = page; const { field, direction } = sort; setPageIndex(index); setPageSize(size); setSortDirection(direction); setSortField(field); - }; + }, []); /** Invoked when the user toggles the option to only view favorite timelines */ - const onToggleOnlyFavorites: OnToggleOnlyFavorites = () => { + const onToggleOnlyFavorites: OnToggleOnlyFavorites = useCallback(() => { setOnlyFavorites(!onlyFavorites); - }; + }, [onlyFavorites]); /** Invoked when the user toggles the expansion or collapse of inline notes in a table row */ - const onToggleShowNotes: OnToggleShowNotes = ( - newItemIdToExpandedNotesRowMap: Record - ) => { - setItemIdToExpandedNotesRowMap(newItemIdToExpandedNotesRowMap); - }; + const onToggleShowNotes: OnToggleShowNotes = useCallback( + (newItemIdToExpandedNotesRowMap: Record) => { + setItemIdToExpandedNotesRowMap(newItemIdToExpandedNotesRowMap); + }, + [] + ); /** Resets the selection state such that all timelines are unselected */ - const resetSelectionState = () => { + const resetSelectionState = useCallback(() => { setSelectedItems([]); - }; + }, []); - const openTimeline: OnOpenTimeline = ({ - duplicate, - timelineId, - }: { - duplicate: boolean; - timelineId: string; - }) => { - if (isModal && closeModalTimeline != null) { - closeModalTimeline(); - } + const openTimeline: OnOpenTimeline = useCallback( + ({ duplicate, timelineId }: { duplicate: boolean; timelineId: string }) => { + if (isModal && closeModalTimeline != null) { + closeModalTimeline(); + } - queryTimelineById({ - apolloClient, - duplicate, - timelineId, - updateIsLoading, - updateTimeline, - }); - }; + queryTimelineById({ + apolloClient, + duplicate, + timelineId, + updateIsLoading, + updateTimeline, + }); + }, + [apolloClient, updateIsLoading, updateTimeline] + ); + + const deleteTimelines: DeleteTimelines = useCallback( + (timelineIds: string[], variables?: AllTimelinesVariables) => { + if (timelineIds.includes(timeline.savedObjectId || '')) { + createNewTimeline({ id: 'timeline-1', columns: defaultHeaders, show: false }); + } + apolloClient.mutate({ + mutation: deleteTimelineMutation, + fetchPolicy: 'no-cache', + variables: { id: timelineIds }, + refetchQueries: [ + { + query: allTimelinesQuery, + variables, + }, + ], + }); + }, + [apolloClient, createNewTimeline, timeline] + ); - const deleteTimelines: DeleteTimelines = ( - timelineIds: string[], - variables?: AllTimelinesVariables - ) => { - if (timelineIds.includes(timeline.savedObjectId || '')) { - createNewTimeline({ id: 'timeline-1', columns: defaultHeaders, show: false }); - } - apolloClient.mutate({ - mutation: deleteTimelineMutation, - fetchPolicy: 'no-cache', - variables: { id: timelineIds }, - refetchQueries: [ - { - query: allTimelinesQuery, - variables, - }, - ], - }); - }; useEffect(() => { focusInput(); }, []); diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/index.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/index.tsx index 41907e07d5c1b..e8242237cd2c8 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/index.tsx @@ -5,7 +5,7 @@ */ import { EuiButtonEmpty, EuiModal, EuiOverlayMask } from '@elastic/eui'; -import React, { useState } from 'react'; +import React, { useCallback, useState } from 'react'; import { ApolloConsumer } from 'react-apollo'; import * as i18n from '../translations'; @@ -29,16 +29,20 @@ export const OpenTimelineModalButton = React.memo( const [showModal, setShowModal] = useState(false); /** shows or hides the `Open Timeline` modal */ - function toggleShowModal() { + const openModal = useCallback(() => { if (onToggle != null) { onToggle(); } - setShowModal(!showModal); - } + setShowModal(true); + }, [onToggle]); + + const closeModal = useCallback(() => { + if (onToggle != null) { + onToggle(); + } + setShowModal(false); + }, [onToggle]); - function closeModalTimeline() { - toggleShowModal(); - } return ( {client => ( @@ -48,7 +52,7 @@ export const OpenTimelineModalButton = React.memo( data-test-subj="open-timeline-button" iconSide="left" iconType="folderOpen" - onClick={toggleShowModal} + onClick={openModal} > {i18n.OPEN_TIMELINE} @@ -58,11 +62,11 @@ export const OpenTimelineModalButton = React.memo( (
{timelineResult.updated != null ? ( - + ) : ( getEmptyTagValue() )} diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_over_time/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_over_time/index.tsx new file mode 100644 index 0000000000000..ad343933a268c --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_over_time/index.tsx @@ -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 React from 'react'; + +import * as i18n from './translation'; +import { getCustomChartData } from './utils'; +import { MatrixOverTimeHistogram, MatrixOverTimeBasicProps } from '../../../matrix_over_time'; + +export const AuthenticationsOverTimeHistogram = (props: MatrixOverTimeBasicProps) => { + const dataKey = 'authenticationsOverTime'; + const { data, ...matrixOverTimeProps } = props; + + const customChartData = getCustomChartData(data); + + return ( + + ); +}; diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_over_time/translation.ts b/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_over_time/translation.ts new file mode 100644 index 0000000000000..c9a21bd348caa --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_over_time/translation.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * 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 AUTHENTICATIONS_COUNT = i18n.translate( + 'xpack.siem.authenticationsOverTime.authenticationCountTitle', + { + defaultMessage: 'Authentications count', + } +); + +export const UNIT = (totalCount: number) => + i18n.translate('xpack.siem.authenticationsOverTime.unit', { + values: { totalCount }, + defaultMessage: `{totalCount, plural, =1 {authentication} other {authentications}}`, + }); diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_over_time/utils.ts b/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_over_time/utils.ts new file mode 100644 index 0000000000000..3cc89eeff6540 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_over_time/utils.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 { groupBy, map, toPairs } from 'lodash/fp'; + +import { ChartSeriesData } from '../../../charts/common'; +import { MatrixOverTimeHistogramData } from '../../../../graphql/types'; +import { KpiHostsChartColors } from '../kpi_hosts/types'; + +const formatToChartDataItem = ([key, value]: [ + string, + MatrixOverTimeHistogramData[] +]): ChartSeriesData => ({ + key, + value, +}); + +const addCustomColors = (item: ChartSeriesData) => { + if (item.key === 'authentication_success') { + item.color = KpiHostsChartColors.authSuccess; + } + + if (item.key === 'authentication_failure') { + item.color = KpiHostsChartColors.authFailure; + } + + return item; +}; + +export const getCustomChartData = (data: MatrixOverTimeHistogramData[]): ChartSeriesData[] => { + const dataGroupedByEvent = groupBy('g', data); + const dataGroupedEntries = toPairs(dataGroupedByEvent); + const formattedChartData = map(formatToChartDataItem, dataGroupedEntries); + + return map(addCustomColors, formattedChartData); +}; diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_table/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_table/index.tsx index fe280c2899327..b9b132b4f50a4 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_table/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_table/index.tsx @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiToolTip } from '@elastic/eui'; -import { FormattedRelative } from '@kbn/i18n/react'; import { has } from 'lodash/fp'; import React from 'react'; import { connect } from 'react-redux'; @@ -18,6 +16,7 @@ import { hostsModel, hostsSelectors, State } from '../../../../store'; import { DragEffects, DraggableWrapper } from '../../../drag_and_drop/draggable_wrapper'; import { escapeDataProviderId } from '../../../drag_and_drop/helpers'; import { getEmptyTagValue } from '../../../empty_value'; +import { FormattedRelativePreferenceDate } from '../../../formatted_date'; import { HostDetailsLink, IPDetailsLink } from '../../../links'; import { Columns, ItemsPerRow, PaginatedTable } from '../../../paginated_table'; import { IS_OPERATOR } from '../../../timeline/data_providers/data_provider'; @@ -200,6 +199,7 @@ const getAuthenticationColumns = (): AuthTableColumns => [ /> ); }, + width: '8%', }, { name: i18n.FAILURES, @@ -237,16 +237,15 @@ const getAuthenticationColumns = (): AuthTableColumns => [ /> ); }, + width: '8%', }, { name: i18n.LAST_SUCCESSFUL_TIME, truncateText: false, hideForMobile: false, render: ({ node }) => - has('lastSuccess.timestamp', node) ? ( - - - + has('lastSuccess.timestamp', node) && node.lastSuccess!.timestamp != null ? ( + ) : ( getEmptyTagValue() ), @@ -291,9 +290,7 @@ const getAuthenticationColumns = (): AuthTableColumns => [ hideForMobile: false, render: ({ node }) => has('lastFailure.timestamp', node) && node.lastFailure!.timestamp != null ? ( - - - + ) : ( getEmptyTagValue() ), diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/events_over_time/translation.ts b/x-pack/legacy/plugins/siem/public/components/page/hosts/events_over_time/translation.ts index 5f68a1a1cae7d..edc9f97193840 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/events_over_time/translation.ts +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/events_over_time/translation.ts @@ -13,13 +13,6 @@ export const EVENT_COUNT_FREQUENCY_BY_ACTION = i18n.translate( } ); -export const LOADING_EVENTS_OVER_TIME = i18n.translate( - 'xpack.siem.eventsOverTime.loadingEventsOverTimeTitle', - { - defaultMessage: 'Loading events histogram', - } -); - export const SHOWING = i18n.translate('xpack.siem.eventsOverTime.showing', { defaultMessage: 'Showing', }); diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/first_last_seen_host/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/hosts/first_last_seen_host/index.tsx index 665f1f46bc7c1..553615e950b8d 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/first_last_seen_host/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/first_last_seen_host/index.tsx @@ -5,15 +5,14 @@ */ import { EuiIcon, EuiLoadingSpinner, EuiText, EuiToolTip } from '@elastic/eui'; -import moment from 'moment'; + import React from 'react'; import { ApolloConsumer } from 'react-apollo'; import { pure } from 'recompose'; import { useFirstLastSeenHostQuery } from '../../../../containers/hosts/first_last_seen'; import { getEmptyTagValue } from '../../../empty_value'; -import { PreferenceFormattedDate } from '../../../formatted_date'; -import { LocalizedDateTooltip } from '../../../localized_date_tooltip'; +import { FormattedRelativePreferenceDate } from '../../../formatted_date'; export enum FirstLastSeenHostType { FIRST_SEEN = 'first-seen', @@ -52,9 +51,7 @@ export const FirstLastSeenHost = pure<{ hostname: string; type: FirstLastSeenHos : !loading && valueSeen != null && ( - - - + )} {!loading && valueSeen == null && getEmptyTagValue()} diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/columns.tsx b/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/columns.tsx index b626df58b007f..8d490d2c152d9 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/columns.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/columns.tsx @@ -5,14 +5,12 @@ */ import { EuiIcon, EuiToolTip } from '@elastic/eui'; -import moment from 'moment'; import React from 'react'; import { DragEffects, DraggableWrapper } from '../../../drag_and_drop/draggable_wrapper'; import { escapeDataProviderId } from '../../../drag_and_drop/helpers'; import { getEmptyTagValue } from '../../../empty_value'; -import { PreferenceFormattedDate } from '../../../formatted_date'; import { HostDetailsLink } from '../../../links'; -import { LocalizedDateTooltip } from '../../../localized_date_tooltip'; +import { FormattedRelativePreferenceDate } from '../../../formatted_date'; import { IS_OPERATOR } from '../../../timeline/data_providers/data_provider'; import { Provider } from '../../../timeline/data_providers/provider'; import { AddFilterToGlobalSearchBar, createFilter } from '../../add_filter_to_global_search_bar'; @@ -75,11 +73,7 @@ export const getHostsColumns = (): HostsTableColumns => [ sortable: true, render: lastSeen => { if (lastSeen != null) { - return ( - - - - ); + return ; } return getEmptyTagValue(); }, diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/index.tsx index 48eea4d5a611a..cdc84c513737d 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useMemo } from 'react'; +import React, { useMemo, useCallback } from 'react'; import { connect } from 'react-redux'; import { ActionCreator } from 'typescript-fsa'; import { StaticIndexPattern } from 'ui/index_patterns'; @@ -115,20 +115,23 @@ const HostsTableComponent = React.memo( updateTableActivePage, updateTableLimit, }) => { - const onChange = (criteria: Criteria) => { - if (criteria.sort != null) { - const sort: HostsSortField = { - field: getSortField(criteria.sort.field), - direction: criteria.sort.direction, - }; - if (sort.direction !== direction || sort.field !== sortField) { - updateHostsSort({ - sort, - hostsType: type, - }); + const onChange = useCallback( + (criteria: Criteria) => { + if (criteria.sort != null) { + const sort: HostsSortField = { + field: getSortField(criteria.sort.field), + direction: criteria.sort.direction, + }; + if (sort.direction !== direction || sort.field !== sortField) { + updateHostsSort({ + sort, + hostsType: type, + }); + } } - } - }; + }, + [direction, sortField, type] + ); const hostsColumns = useMemo(() => getHostsColumns(), []); diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/kpi_hosts/kpi_host_details_mapping.ts b/x-pack/legacy/plugins/siem/public/components/page/hosts/kpi_hosts/kpi_host_details_mapping.ts index dbaf35c5b0636..59f8e55c46106 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/kpi_hosts/kpi_host_details_mapping.ts +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/kpi_hosts/kpi_host_details_mapping.ts @@ -6,11 +6,7 @@ import * as i18n from './translations'; import { StatItems } from '../../../stat_items'; - -const euiColorVis0 = '#00B3A4'; -const euiColorVis2 = '#DB1374'; -const euiColorVis3 = '#490092'; -const euiColorVis9 = '#920000'; +import { KpiHostsChartColors } from './types'; export const kpiHostDetailsMapping: Readonly = [ { @@ -22,7 +18,7 @@ export const kpiHostDetailsMapping: Readonly = [ name: i18n.SUCCESS_CHART_LABEL, description: i18n.SUCCESS_UNIT_LABEL, value: null, - color: euiColorVis0, + color: KpiHostsChartColors.authSuccess, icon: 'check', }, { @@ -30,7 +26,7 @@ export const kpiHostDetailsMapping: Readonly = [ name: i18n.FAIL_CHART_LABEL, description: i18n.FAIL_UNIT_LABEL, value: null, - color: euiColorVis9, + color: KpiHostsChartColors.authFailure, icon: 'cross', }, ], @@ -48,7 +44,7 @@ export const kpiHostDetailsMapping: Readonly = [ name: i18n.SOURCE_CHART_LABEL, description: i18n.SOURCE_UNIT_LABEL, value: null, - color: euiColorVis2, + color: KpiHostsChartColors.uniqueSourceIps, icon: 'visMapCoordinate', }, { @@ -56,7 +52,7 @@ export const kpiHostDetailsMapping: Readonly = [ name: i18n.DESTINATION_CHART_LABEL, description: i18n.DESTINATION_UNIT_LABEL, value: null, - color: euiColorVis3, + color: KpiHostsChartColors.uniqueDestinationIps, icon: 'visMapCoordinate', }, ], diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/kpi_hosts/kpi_hosts_mapping.ts b/x-pack/legacy/plugins/siem/public/components/page/hosts/kpi_hosts/kpi_hosts_mapping.ts index ca91b49b27185..e2d6348d05840 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/kpi_hosts/kpi_hosts_mapping.ts +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/kpi_hosts/kpi_hosts_mapping.ts @@ -6,12 +6,7 @@ import * as i18n from './translations'; import { StatItems } from '../../../stat_items'; - -const euiColorVis0 = '#00B3A4'; -const euiColorVis1 = '#3185FC'; -const euiColorVis2 = '#DB1374'; -const euiColorVis3 = '#490092'; -const euiColorVis9 = '#920000'; +import { KpiHostsChartColors } from './types'; export const kpiHostsMapping: Readonly = [ { @@ -21,7 +16,7 @@ export const kpiHostsMapping: Readonly = [ { key: 'hosts', value: null, - color: euiColorVis1, + color: KpiHostsChartColors.hosts, icon: 'storage', }, ], @@ -38,7 +33,7 @@ export const kpiHostsMapping: Readonly = [ name: i18n.SUCCESS_CHART_LABEL, description: i18n.SUCCESS_UNIT_LABEL, value: null, - color: euiColorVis0, + color: KpiHostsChartColors.authSuccess, icon: 'check', }, { @@ -46,7 +41,7 @@ export const kpiHostsMapping: Readonly = [ name: i18n.FAIL_CHART_LABEL, description: i18n.FAIL_UNIT_LABEL, value: null, - color: euiColorVis9, + color: KpiHostsChartColors.authFailure, icon: 'cross', }, ], @@ -64,7 +59,7 @@ export const kpiHostsMapping: Readonly = [ name: i18n.SOURCE_CHART_LABEL, description: i18n.SOURCE_UNIT_LABEL, value: null, - color: euiColorVis2, + color: KpiHostsChartColors.uniqueSourceIps, icon: 'visMapCoordinate', }, { @@ -72,7 +67,7 @@ export const kpiHostsMapping: Readonly = [ name: i18n.DESTINATION_CHART_LABEL, description: i18n.DESTINATION_UNIT_LABEL, value: null, - color: euiColorVis3, + color: KpiHostsChartColors.uniqueDestinationIps, icon: 'visMapCoordinate', }, ], diff --git a/x-pack/legacy/plugins/code/common/installation.ts b/x-pack/legacy/plugins/siem/public/components/page/hosts/kpi_hosts/types.ts similarity index 58% rename from x-pack/legacy/plugins/code/common/installation.ts rename to x-pack/legacy/plugins/siem/public/components/page/hosts/kpi_hosts/types.ts index c6a9b7383297b..f2f50d72952ac 100644 --- a/x-pack/legacy/plugins/code/common/installation.ts +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/kpi_hosts/types.ts @@ -4,7 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -export enum InstallationType { - Embed, - Plugin, +export enum KpiHostsChartColors { + authSuccess = '#00B3A4', + authFailure = '#920000', + uniqueSourceIps = '#DB1374', + uniqueDestinationIps = '#490092', + hosts = '#3185FC', } diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/uncommon_process_table/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/hosts/uncommon_process_table/index.tsx index 6fd2cab90efea..2f2d84306e25e 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/uncommon_process_table/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/uncommon_process_table/index.tsx @@ -161,16 +161,20 @@ const getUncommonColumns = (): UncommonProcessTableColumns => [ width: '20%', }, { + align: 'right', name: i18n.NUMBER_OF_HOSTS, truncateText: false, hideForMobile: false, render: ({ node }) => <>{node.hosts != null ? node.hosts.length : getEmptyValue()}, + width: '8%', }, { + align: 'right', name: i18n.NUMBER_OF_INSTANCES, truncateText: false, hideForMobile: false, render: ({ node }) => defaultToEmptyTag(node.instances), + width: '8%', }, { name: i18n.HOSTS, diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/ip_overview/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/ip_overview/index.tsx index 7c695e37386ef..b71d786e643eb 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/ip_overview/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/ip_overview/index.tsx @@ -114,8 +114,14 @@ export const IpOverview = pure( const descriptionLists: Readonly = [ firstColumn, [ - { title: i18n.FIRST_SEEN, description: dateRenderer('firstSeen', typeData) }, - { title: i18n.LAST_SEEN, description: dateRenderer('lastSeen', typeData) }, + { + title: i18n.FIRST_SEEN, + description: typeData ? dateRenderer(typeData.firstSeen) : getEmptyTagValue(), + }, + { + title: i18n.LAST_SEEN, + description: typeData ? dateRenderer(typeData.lastSeen) : getEmptyTagValue(), + }, ], [ { diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/columns.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/columns.tsx index 353699c5158bc..b1c1b26cd498d 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/columns.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/columns.tsx @@ -69,6 +69,7 @@ export const getNetworkDnsColumns = (type: networkModel.NetworkType): NetworkDns }, }, { + align: 'right', field: `node.${NetworkDnsFields.queryCount}`, name: i18n.TOTAL_QUERIES, sortable: true, @@ -83,6 +84,7 @@ export const getNetworkDnsColumns = (type: networkModel.NetworkType): NetworkDns }, }, { + align: 'right', field: `node.${NetworkDnsFields.uniqueDomains}`, name: i18n.UNIQUE_DOMAINS, sortable: true, @@ -97,6 +99,7 @@ export const getNetworkDnsColumns = (type: networkModel.NetworkType): NetworkDns }, }, { + align: 'right', field: `node.${NetworkDnsFields.dnsBytesIn}`, name: i18n.DNS_BYTES_IN, sortable: true, @@ -111,6 +114,7 @@ export const getNetworkDnsColumns = (type: networkModel.NetworkType): NetworkDns }, }, { + align: 'right', field: `node.${NetworkDnsFields.dnsBytesOut}`, name: i18n.DNS_BYTES_OUT, sortable: true, diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/index.tsx index c1c1bec80d676..ac5470ee4f236 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/index.tsx @@ -5,7 +5,7 @@ */ import { isEqual } from 'lodash/fp'; -import React from 'react'; +import React, { useCallback } from 'react'; import { connect } from 'react-redux'; import { ActionCreator } from 'typescript-fsa'; @@ -77,24 +77,34 @@ export const NetworkDnsTableComponent = React.memo( type, updateNetworkTable, }) => { - const onChange = (criteria: Criteria) => { - if (criteria.sort != null) { - const newDnsSortField: NetworkDnsSortField = { - field: criteria.sort.field.split('.')[1] as NetworkDnsFields, - direction: criteria.sort.direction, - }; - if (!isEqual(newDnsSortField, sort)) { - updateNetworkTable({ networkType: type, tableType, updates: { sort: newDnsSortField } }); + const onChange = useCallback( + (criteria: Criteria) => { + if (criteria.sort != null) { + const newDnsSortField: NetworkDnsSortField = { + field: criteria.sort.field.split('.')[1] as NetworkDnsFields, + direction: criteria.sort.direction, + }; + if (!isEqual(newDnsSortField, sort)) { + updateNetworkTable({ + networkType: type, + tableType, + updates: { sort: newDnsSortField }, + }); + } } - } - }; + }, + [sort, type] + ); - const onChangePtrIncluded = () => - updateNetworkTable({ - networkType: type, - tableType, - updates: { isPtrIncluded: !isPtrIncluded }, - }); + const onChangePtrIncluded = useCallback( + () => + updateNetworkTable({ + networkType: type, + tableType, + updates: { isPtrIncluded: !isPtrIncluded }, + }), + [type, isPtrIncluded] + ); return ( ( type, updateNetworkTable, }) => { - const onChange = (criteria: Criteria, tableType: networkModel.TopNTableType) => { - if (criteria.sort != null) { - const splitField = criteria.sort.field.split('.'); - const field = last(splitField); - const newSortDirection = field !== sort.field ? Direction.desc : criteria.sort.direction; // sort by desc on init click - const newTopNFlowSort: NetworkTopTablesSortField = { - field: field as NetworkTopTablesFields, - direction: newSortDirection, - }; - if (!isEqual(newTopNFlowSort, sort)) { - updateNetworkTable({ - networkType: type, - tableType, - updates: { - sort: newTopNFlowSort, - }, - }); + const onChange = useCallback( + (criteria: Criteria, tableType: networkModel.TopNTableType) => { + if (criteria.sort != null) { + const splitField = criteria.sort.field.split('.'); + const field = last(splitField); + const newSortDirection = field !== sort.field ? Direction.desc : criteria.sort.direction; // sort by desc on init click + const newTopNFlowSort: NetworkTopTablesSortField = { + field: field as NetworkTopTablesFields, + direction: newSortDirection, + }; + if (!isEqual(newTopNFlowSort, sort)) { + updateNetworkTable({ + networkType: type, + tableType, + updates: { + sort: newTopNFlowSort, + }, + }); + } } - } - }; + }, + [sort, type] + ); let tableType: networkModel.TopNTableType; const headerTitle: string = diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/index.tsx index 3a4712e53c4d4..6adb335839982 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/index.tsx @@ -5,7 +5,7 @@ */ import { isEqual } from 'lodash/fp'; -import React from 'react'; +import React, { useCallback } from 'react'; import { connect } from 'react-redux'; import { compose } from 'redux'; import { ActionCreator } from 'typescript-fsa'; @@ -78,22 +78,26 @@ const TlsTableComponent = React.memo( type === networkModel.NetworkType.page ? networkModel.NetworkTableType.tls : networkModel.IpDetailsTableType.tls; - const onChange = (criteria: Criteria) => { - if (criteria.sort != null) { - const splitField = criteria.sort.field.split('.'); - const newTlsSort: TlsSortField = { - field: getSortFromString(splitField[splitField.length - 1]), - direction: criteria.sort.direction, - }; - if (!isEqual(newTlsSort, sort)) { - updateNetworkTable({ - networkType: type, - tableType, - updates: { sort: newTlsSort }, - }); + + const onChange = useCallback( + (criteria: Criteria) => { + if (criteria.sort != null) { + const splitField = criteria.sort.field.split('.'); + const newTlsSort: TlsSortField = { + field: getSortFromString(splitField[splitField.length - 1]), + direction: criteria.sort.direction, + }; + if (!isEqual(newTlsSort, sort)) { + updateNetworkTable({ + networkType: type, + tableType, + updates: { sort: newTlsSort }, + }); + } } - } - }; + }, + [sort, type] + ); return ( ( updateNetworkTable, sort, }) => { - const onChange = (criteria: Criteria) => { - if (criteria.sort != null) { - const splitField = criteria.sort.field.split('.'); - const newUsersSort: UsersSortField = { - field: getSortFromString(splitField[splitField.length - 1]), - direction: criteria.sort.direction, - }; - if (!isEqual(newUsersSort, sort)) { - updateNetworkTable({ - networkType: type, - tableType, - updates: { sort: newUsersSort }, - }); + const onChange = useCallback( + (criteria: Criteria) => { + if (criteria.sort != null) { + const splitField = criteria.sort.field.split('.'); + const newUsersSort: UsersSortField = { + field: getSortFromString(splitField[splitField.length - 1]), + direction: criteria.sort.direction, + }; + if (!isEqual(newUsersSort, sort)) { + updateNetworkTable({ + networkType: type, + tableType, + updates: { sort: newUsersSort }, + }); + } } - } - }; + }, + [sort, type] + ); return ( { - const isQuickSelection = - payload.dateRange.from.includes('now') || payload.dateRange.to.includes('now'); - let updateSearchBar: UpdateReduxSearchBar = { - id, - end: toStr != null ? toStr : new Date(end).toISOString(), - start: fromStr != null ? fromStr : new Date(start).toISOString(), - isInvalid: false, - isQuickSelection, - updateTime: false, - }; - let isStateUpdated = false; - - if ( - (isQuickSelection && - (fromStr !== payload.dateRange.from || toStr !== payload.dateRange.to)) || - (!isQuickSelection && - (start !== formatDate(payload.dateRange.from) || - end !== formatDate(payload.dateRange.to))) - ) { - isStateUpdated = true; - updateSearchBar.updateTime = true; - updateSearchBar.end = payload.dateRange.to; - updateSearchBar.start = payload.dateRange.from; - } - - if (payload.query != null && !isEqual(payload.query, filterQuery)) { - isStateUpdated = true; - updateSearchBar = set('query', payload.query, updateSearchBar); - } - - if (!isStateUpdated) { - // That mean we are doing a refresh! - if (isQuickSelection) { + const onQuerySubmit = useCallback( + (payload: { dateRange: TimeRange; query?: Query }) => { + const isQuickSelection = + payload.dateRange.from.includes('now') || payload.dateRange.to.includes('now'); + let updateSearchBar: UpdateReduxSearchBar = { + id, + end: toStr != null ? toStr : new Date(end).toISOString(), + start: fromStr != null ? fromStr : new Date(start).toISOString(), + isInvalid: false, + isQuickSelection, + updateTime: false, + }; + let isStateUpdated = false; + + if ( + (isQuickSelection && + (fromStr !== payload.dateRange.from || toStr !== payload.dateRange.to)) || + (!isQuickSelection && + (start !== formatDate(payload.dateRange.from) || + end !== formatDate(payload.dateRange.to))) + ) { + isStateUpdated = true; updateSearchBar.updateTime = true; updateSearchBar.end = payload.dateRange.to; updateSearchBar.start = payload.dateRange.from; - } else { - queries.forEach(q => q.refetch && (q.refetch as inputsModel.Refetch)()); } - } - window.setTimeout(() => updateSearch(updateSearchBar), 0); - }; + if (payload.query != null && !isEqual(payload.query, filterQuery)) { + isStateUpdated = true; + updateSearchBar = set('query', payload.query, updateSearchBar); + } - const onRefresh = (payload: { dateRange: TimeRange }) => { - if (payload.dateRange.from.includes('now') || payload.dateRange.to.includes('now')) { - updateSearch({ - id, - end: payload.dateRange.to, - start: payload.dateRange.from, - isInvalid: false, - isQuickSelection: true, - updateTime: true, - }); - } else { - queries.forEach(q => q.refetch && (q.refetch as inputsModel.Refetch)()); - } - }; + if (!isStateUpdated) { + // That mean we are doing a refresh! + if (isQuickSelection) { + updateSearchBar.updateTime = true; + updateSearchBar.end = payload.dateRange.to; + updateSearchBar.start = payload.dateRange.from; + } else { + queries.forEach(q => q.refetch && (q.refetch as inputsModel.Refetch)()); + } + } - const onSaved = (newSavedQuery: SavedQuery) => { - setSavedQuery({ id, savedQuery: newSavedQuery }); - }; + window.setTimeout(() => updateSearch(updateSearchBar), 0); + }, + [id, end, filterQuery, fromStr, queries, start, toStr] + ); - const onSavedQueryUpdated = (savedQueryUpdated: SavedQuery) => { - const isQuickSelection = savedQueryUpdated.attributes.timefilter - ? savedQueryUpdated.attributes.timefilter.from.includes('now') || - savedQueryUpdated.attributes.timefilter.to.includes('now') - : false; + const onRefresh = useCallback( + (payload: { dateRange: TimeRange }) => { + if (payload.dateRange.from.includes('now') || payload.dateRange.to.includes('now')) { + updateSearch({ + id, + end: payload.dateRange.to, + start: payload.dateRange.from, + isInvalid: false, + isQuickSelection: true, + updateTime: true, + }); + } else { + queries.forEach(q => q.refetch && (q.refetch as inputsModel.Refetch)()); + } + }, + [id, queries] + ); - let updateSearchBar: UpdateReduxSearchBar = { - id, - filters: savedQueryUpdated.attributes.filters || [], - end: toStr != null ? toStr : new Date(end).toISOString(), - start: fromStr != null ? fromStr : new Date(start).toISOString(), - isInvalid: false, - isQuickSelection, - updateTime: false, - }; + const onSaved = useCallback( + (newSavedQuery: SavedQuery) => { + setSavedQuery({ id, savedQuery: newSavedQuery }); + }, + [id] + ); - if (savedQueryUpdated.attributes.timefilter) { - updateSearchBar.end = savedQueryUpdated.attributes.timefilter - ? savedQueryUpdated.attributes.timefilter.to - : updateSearchBar.end; - updateSearchBar.start = savedQueryUpdated.attributes.timefilter - ? savedQueryUpdated.attributes.timefilter.from - : updateSearchBar.start; - updateSearchBar.updateTime = true; - } + const onSavedQueryUpdated = useCallback( + (savedQueryUpdated: SavedQuery) => { + const isQuickSelection = savedQueryUpdated.attributes.timefilter + ? savedQueryUpdated.attributes.timefilter.from.includes('now') || + savedQueryUpdated.attributes.timefilter.to.includes('now') + : false; + + let updateSearchBar: UpdateReduxSearchBar = { + id, + filters: savedQueryUpdated.attributes.filters || [], + end: toStr != null ? toStr : new Date(end).toISOString(), + start: fromStr != null ? fromStr : new Date(start).toISOString(), + isInvalid: false, + isQuickSelection, + updateTime: false, + }; + + if (savedQueryUpdated.attributes.timefilter) { + updateSearchBar.end = savedQueryUpdated.attributes.timefilter + ? savedQueryUpdated.attributes.timefilter.to + : updateSearchBar.end; + updateSearchBar.start = savedQueryUpdated.attributes.timefilter + ? savedQueryUpdated.attributes.timefilter.from + : updateSearchBar.start; + updateSearchBar.updateTime = true; + } - updateSearchBar = set('query', savedQueryUpdated.attributes.query, updateSearchBar); - updateSearchBar = set('savedQuery', savedQueryUpdated, updateSearchBar); + updateSearchBar = set('query', savedQueryUpdated.attributes.query, updateSearchBar); + updateSearchBar = set('savedQuery', savedQueryUpdated, updateSearchBar); - updateSearch(updateSearchBar); - }; + updateSearch(updateSearchBar); + }, + [id, end, fromStr, start, toStr] + ); - const onClearSavedQuery = () => { + const onClearSavedQuery = useCallback(() => { if (savedQuery != null) { updateSearch({ id, @@ -222,7 +234,7 @@ const SearchBarComponent = memo { let isSubscribed = true; @@ -246,13 +258,13 @@ const SearchBarComponent = memo [indexPattern as IndexPattern], [indexPattern]); return ( ( const [recentlyUsedRanges, setRecentlyUsedRanges] = useState( [] ); - const onRefresh = ({ start: newStart, end: newEnd }: OnRefreshProps): void => { - updateReduxTime({ - end: newEnd, - id, - isInvalid: false, - isQuickSelection, - kql: kqlQuery, - start: newStart, - timelineId, - }); - const currentStart = formatDate(newStart); - const currentEnd = isQuickSelection - ? formatDate(newEnd, { roundUp: true }) - : formatDate(newEnd); - if (!isQuickSelection || (start === currentStart && end === currentEnd)) { - refetchQuery(queries); - } - }; - - const onRefreshChange = ({ isPaused, refreshInterval }: OnRefreshChangeProps): void => { - if (duration !== refreshInterval) { - setDuration({ id, duration: refreshInterval }); - } - - if (isPaused && policy === 'interval') { - stopAutoReload({ id }); - } else if (!isPaused && policy === 'manual') { - startAutoReload({ id }); - } - - if (!isPaused && (!isQuickSelection || (isQuickSelection && toStr !== 'now'))) { - refetchQuery(queries); - } - }; - - const refetchQuery = (newQueries: inputsModel.GlobalGraphqlQuery[]) => { - newQueries.forEach(q => q.refetch && (q.refetch as inputsModel.Refetch)()); - }; - - const onTimeChange = ({ - start: newStart, - end: newEnd, - isQuickSelection: newIsQuickSelection, - isInvalid, - }: OnTimeChangeProps) => { - if (!isInvalid) { + const onRefresh = useCallback( + ({ start: newStart, end: newEnd }: OnRefreshProps): void => { updateReduxTime({ end: newEnd, id, - isInvalid, - isQuickSelection: newIsQuickSelection, + isInvalid: false, + isQuickSelection, kql: kqlQuery, start: newStart, timelineId, }); - const newRecentlyUsedRanges = [ - { start: newStart, end: newEnd }, - ...take( - MAX_RECENTLY_USED_RANGES, - recentlyUsedRanges.filter( - recentlyUsedRange => - !(recentlyUsedRange.start === newStart && recentlyUsedRange.end === newEnd) - ) - ), - ]; + const currentStart = formatDate(newStart); + const currentEnd = isQuickSelection + ? formatDate(newEnd, { roundUp: true }) + : formatDate(newEnd); + if (!isQuickSelection || (start === currentStart && end === currentEnd)) { + refetchQuery(queries); + } + }, + [end, id, isQuickSelection, kqlQuery, start, timelineId] + ); + + const onRefreshChange = useCallback( + ({ isPaused, refreshInterval }: OnRefreshChangeProps): void => { + if (duration !== refreshInterval) { + setDuration({ id, duration: refreshInterval }); + } - setRecentlyUsedRanges(newRecentlyUsedRanges); - setIsQuickSelection(newIsQuickSelection); - } + if (isPaused && policy === 'interval') { + stopAutoReload({ id }); + } else if (!isPaused && policy === 'manual') { + startAutoReload({ id }); + } + + if (!isPaused && (!isQuickSelection || (isQuickSelection && toStr !== 'now'))) { + refetchQuery(queries); + } + }, + [id, isQuickSelection, duration, policy, toStr] + ); + + const refetchQuery = (newQueries: inputsModel.GlobalGraphqlQuery[]) => { + newQueries.forEach(q => q.refetch && (q.refetch as inputsModel.Refetch)()); }; + + const onTimeChange = useCallback( + ({ + start: newStart, + end: newEnd, + isQuickSelection: newIsQuickSelection, + isInvalid, + }: OnTimeChangeProps) => { + if (!isInvalid) { + updateReduxTime({ + end: newEnd, + id, + isInvalid, + isQuickSelection: newIsQuickSelection, + kql: kqlQuery, + start: newStart, + timelineId, + }); + const newRecentlyUsedRanges = [ + { start: newStart, end: newEnd }, + ...take( + MAX_RECENTLY_USED_RANGES, + recentlyUsedRanges.filter( + recentlyUsedRange => + !(recentlyUsedRange.start === newStart && recentlyUsedRange.end === newEnd) + ) + ), + ]; + + setRecentlyUsedRanges(newRecentlyUsedRanges); + setIsQuickSelection(newIsQuickSelection); + } + }, + [recentlyUsedRanges, kqlQuery] + ); + const endDate = kind === 'relative' ? toStr : new Date(end).toISOString(); const startDate = kind === 'relative' ? fromStr : new Date(start).toISOString(); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/events/event_column_view.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/events/event_column_view.tsx index 7afa6ca70ff9b..96cb9f754f525 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/events/event_column_view.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/events/event_column_view.tsx @@ -15,7 +15,7 @@ import { EventsTrData } from '../../styles'; import { Actions } from '../actions'; import { ColumnHeader } from '../column_headers/column_header'; import { DataDrivenColumns } from '../data_driven_columns'; -import { eventHasNotes, eventIsPinned, getPinOnClick } from '../helpers'; +import { eventHasNotes, getPinOnClick } from '../helpers'; import { ColumnRenderer } from '../renderers/column_renderer'; interface Props { @@ -28,13 +28,13 @@ interface Props { eventIdToNoteIds: Readonly>; expanded: boolean; getNotesByIds: (noteIds: string[]) => Note[]; + isEventPinned: boolean; isEventViewer?: boolean; loading: boolean; onColumnResized: OnColumnResized; onEventToggled: () => void; onPinEvent: OnPinEvent; onUnPinEvent: OnUnPinEvent; - pinnedEventIds: Readonly>; showNotes: boolean; timelineId: string; toggleShowNotes: () => void; @@ -56,13 +56,13 @@ export const EventColumnView = React.memo( eventIdToNoteIds, expanded, getNotesByIds, + isEventPinned = false, isEventViewer = false, loading, onColumnResized, onEventToggled, onPinEvent, onUnPinEvent, - pinnedEventIds, showNotes, timelineId, toggleShowNotes, @@ -76,10 +76,7 @@ export const EventColumnView = React.memo( expanded={expanded} data-test-subj="actions" eventId={id} - eventIsPinned={eventIsPinned({ - eventId: id, - pinnedEventIds, - })} + eventIsPinned={isEventPinned} getNotesByIds={getNotesByIds} isEventViewer={isEventViewer} loading={loading} @@ -90,7 +87,7 @@ export const EventColumnView = React.memo( eventId: id, onPinEvent, onUnPinEvent, - pinnedEventIds, + isEventPinned, })} showCheckboxes={false} showNotes={showNotes} @@ -118,7 +115,7 @@ export const EventColumnView = React.memo( prevProps.eventIdToNoteIds === nextProps.eventIdToNoteIds && prevProps.expanded === nextProps.expanded && prevProps.loading === nextProps.loading && - prevProps.pinnedEventIds === nextProps.pinnedEventIds && + prevProps.isEventPinned === nextProps.isEventPinned && prevProps.showNotes === nextProps.showNotes && prevProps.timelineId === nextProps.timelineId ); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/events/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/events/index.tsx index 78992dceea4dc..34281219bcc0c 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/events/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/events/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { BrowserFields } from '../../../../containers/source'; import { TimelineItem } from '../../../../graphql/types'; @@ -17,6 +17,7 @@ import { ColumnHeader } from '../column_headers/column_header'; import { ColumnRenderer } from '../renderers/column_renderer'; import { RowRenderer } from '../renderers/row_renderer'; import { StatefulEvent } from './stateful_event'; +import { eventIsPinned } from '../helpers'; interface Props { actionsColumnWidth: number; @@ -74,6 +75,7 @@ export const Events = React.memo( event={event} eventIdToNoteIds={eventIdToNoteIds} getNotesByIds={getNotesByIds} + isEventPinned={eventIsPinned({ eventId: event._id, pinnedEventIds })} isEventViewer={isEventViewer} key={event._id} maxDelay={maxDelay(i)} @@ -81,7 +83,6 @@ export const Events = React.memo( onPinEvent={onPinEvent} onUnPinEvent={onUnPinEvent} onUpdateColumns={onUpdateColumns} - pinnedEventIds={pinnedEventIds} rowRenderers={rowRenderers} timelineId={id} toggleColumn={toggleColumn} diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/events/stateful_event.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/events/stateful_event.tsx index 766a75c05f17c..d54fe8df28a85 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/events/stateful_event.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/events/stateful_event.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useRef, useState, useCallback } from 'react'; import uuid from 'uuid'; import VisibilitySensor from 'react-visibility-sensor'; @@ -21,7 +21,6 @@ import { STATEFUL_EVENT_CSS_CLASS_NAME } from '../../helpers'; import { EventsTrGroup, EventsTrSupplement, OFFSET_SCROLLBAR } from '../../styles'; import { useTimelineWidthContext } from '../../timeline_context'; import { ColumnHeader } from '../column_headers/column_header'; -import { eventIsPinned } from '../helpers'; import { ColumnRenderer } from '../renderers/column_renderer'; import { getRowRenderer } from '../renderers/get_row_renderer'; import { RowRenderer } from '../renderers/row_renderer'; @@ -42,7 +41,7 @@ interface Props { onPinEvent: OnPinEvent; onUnPinEvent: OnUnPinEvent; onUpdateColumns: OnUpdateColumns; - pinnedEventIds: Readonly>; + isEventPinned: boolean; rowRenderers: RowRenderer[]; timelineId: string; toggleColumn: (column: ColumnHeader) => void; @@ -110,12 +109,12 @@ export const StatefulEvent = React.memo( eventIdToNoteIds, getNotesByIds, isEventViewer = false, + isEventPinned = false, maxDelay = 0, onColumnResized, onPinEvent, onUnPinEvent, onUpdateColumns, - pinnedEventIds, rowRenderers, timelineId, toggleColumn, @@ -127,27 +126,28 @@ export const StatefulEvent = React.memo( const divElement = useRef(null); - const onToggleShowNotes = (eventId: string): (() => void) => () => { + const onToggleShowNotes = useCallback(() => { + const eventId = event._id; setShowNotes({ ...showNotes, [eventId]: !showNotes[eventId] }); - }; + }, [event, showNotes]); - const onToggleExpanded = (eventId: string): (() => void) => () => { + const onToggleExpanded = useCallback(() => { + const eventId = event._id; setExpanded({ ...expanded, [eventId]: !expanded[eventId], }); - }; + }, [event, expanded]); - const associateNote = ( - eventId: string, - addNoteToEventChild: AddNoteToEvent, - onPinEventChild: OnPinEvent - ): ((noteId: string) => void) => (noteId: string) => { - addNoteToEventChild({ eventId, noteId }); - if (!eventIsPinned({ eventId, pinnedEventIds })) { - onPinEventChild(eventId); // pin the event, because it has notes - } - }; + const associateNote = useCallback( + (noteId: string) => { + addNoteToEvent({ eventId: event._id, noteId }); + if (!isEventPinned) { + onPinEvent(event._id); // pin the event, because it has notes + } + }, + [addNoteToEvent, event, isEventPinned, onPinEvent] + ); /** * Incrementally loads the events when it mounts by trying to @@ -155,7 +155,6 @@ export const StatefulEvent = React.memo( * indicate to React that it should render its self by setting * its initialRender to true. */ - useEffect(() => { let _isMounted = true; @@ -222,6 +221,7 @@ export const StatefulEvent = React.memo( expanded={!!expanded[event._id]} getNotesByIds={getNotesByIds} id={event._id} + isEventPinned={isEventPinned} isEventViewer={isEventViewer} loading={loading} onColumnResized={onColumnResized} @@ -229,7 +229,6 @@ export const StatefulEvent = React.memo( onToggleExpanded={onToggleExpanded} onToggleShowNotes={onToggleShowNotes} onUnPinEvent={onUnPinEvent} - pinnedEventIds={pinnedEventIds} showNotes={!!showNotes[event._id]} timelineId={timelineId} updateNote={updateNote} diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/events/stateful_event_child.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/events/stateful_event_child.tsx index 4a623ce99c9e9..668139349a377 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/events/stateful_event_child.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/events/stateful_event_child.tsx @@ -29,21 +29,17 @@ interface Props { expanded: boolean; eventIdToNoteIds: Readonly>; isEventViewer?: boolean; + isEventPinned: boolean; loading: boolean; onColumnResized: OnColumnResized; onUnPinEvent: OnUnPinEvent; - pinnedEventIds: Readonly>; showNotes: boolean; timelineId: string; updateNote: UpdateNote; - onToggleExpanded: (eventId: string) => () => void; - onToggleShowNotes: (eventId: string) => () => void; + onToggleExpanded: () => void; + onToggleShowNotes: () => void; getNotesByIds: (noteIds: string[]) => Note[]; - associateNote: ( - eventId: string, - addNoteToEvent: AddNoteToEvent, - onPinEvent: OnPinEvent - ) => (noteId: string) => void; + associateNote: (noteId: string) => void; } export const getNewNoteId = (): string => uuid.v4(); @@ -64,11 +60,11 @@ export const StatefulEventChild = React.memo( eventIdToNoteIds, getNotesByIds, isEventViewer = false, + isEventPinned = false, loading, onColumnResized, onToggleExpanded, onUnPinEvent, - pinnedEventIds, showNotes, timelineId, onToggleShowNotes, @@ -84,23 +80,23 @@ export const StatefulEventChild = React.memo( @@ -110,13 +106,13 @@ export const StatefulEventChild = React.memo( style={{ width: `${width - OFFSET_SCROLLBAR}px` }} > diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/helpers.ts b/x-pack/legacy/plugins/siem/public/components/timeline/body/helpers.ts index 1cd83cb5560ea..0b1d21b2371ee 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/helpers.ts +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/helpers.ts @@ -55,7 +55,7 @@ export interface GetPinOnClickParams { eventId: string; onPinEvent: OnPinEvent; onUnPinEvent: OnUnPinEvent; - pinnedEventIds: Readonly>; + isEventPinned: boolean; } export const getPinOnClick = ({ @@ -63,15 +63,12 @@ export const getPinOnClick = ({ eventId, onPinEvent, onUnPinEvent, - pinnedEventIds, + isEventPinned, }: GetPinOnClickParams): (() => void) => { if (!allowUnpinning) { return noop; } - - return eventIsPinned({ eventId, pinnedEventIds }) - ? () => onUnPinEvent(eventId) - : () => onPinEvent(eventId); + return isEventPinned ? () => onUnPinEvent(eventId) : () => onPinEvent(eventId); }; export const getColumnWidthFromType = (type: string): number => diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/index.test.tsx index 73b208757c7ad..86a6ebe22799b 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/index.test.tsx @@ -215,7 +215,7 @@ describe('Body', () => { wrapper.update(); wrapper .find('[data-test-subj="new-note-tabs"] textarea') - .simulate('change', { target: { value: 'hello world' } }); + .simulate('change', { target: { value: note } }); wrapper.update(); wrapper .find('button[data-test-subj="add-note"]') @@ -314,13 +314,11 @@ describe('Body', () => { /> ); addaNoteToEvent(wrapper, 'hello world'); - dispatchAddNoteToEvent.mockClear(); dispatchOnPinEvent.mockClear(); wrapper.setProps({ pinnedEventIds: { 1: true } }); wrapper.update(); addaNoteToEvent(wrapper, 'new hello world'); - expect(dispatchAddNoteToEvent).toHaveBeenCalled(); expect(dispatchOnPinEvent).not.toHaveBeenCalled(); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/stateful_body.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/stateful_body.tsx index d93446b2af95b..531e61dd7dc60 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/stateful_body.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/stateful_body.tsx @@ -6,14 +6,14 @@ import { noop } from 'lodash/fp'; import memoizeOne from 'memoize-one'; -import * as React from 'react'; +import React, { useCallback } from 'react'; import { connect } from 'react-redux'; import { ActionCreator } from 'typescript-fsa'; import { BrowserFields } from '../../../containers/source'; import { TimelineItem } from '../../../graphql/types'; import { Note } from '../../../lib/note'; -import { appSelectors, State, timelineSelectors } from '../../../store'; +import { appModel, appSelectors, State, timelineSelectors } from '../../../store'; import { AddNoteToEvent, UpdateNote } from '../../notes/helpers'; import { OnColumnRemoved, @@ -45,7 +45,7 @@ interface OwnProps { interface ReduxProps { columnHeaders: ColumnHeader[]; eventIdToNoteIds: Readonly>; - getNotesByIds: (noteIds: string[]) => Note[]; + notesById: appModel.NotesById; pinnedEventIds: Readonly>; range?: string; } @@ -92,10 +92,10 @@ const StatefulBodyComponent = React.memo( columnHeaders, data, eventIdToNoteIds, - getNotesByIds, height, id, isEventViewer = false, + notesById, pinEvent, pinnedEventIds, range, @@ -107,30 +107,44 @@ const StatefulBodyComponent = React.memo( updateNote, updateSort, }) => { - const onAddNoteToEvent: AddNoteToEvent = ({ - eventId, - noteId, - }: { - eventId: string; - noteId: string; - }) => addNoteToEvent!({ id, eventId, noteId }); - - const onColumnSorted: OnColumnSorted = sorted => { - updateSort!({ id, sort: sorted }); - }; + const getNotesByIds = useCallback( + (noteIds: string[]): Note[] => appSelectors.getNotes(notesById, noteIds), + [notesById] + ); - const onColumnRemoved: OnColumnRemoved = columnId => removeColumn!({ id, columnId }); + const onAddNoteToEvent: AddNoteToEvent = useCallback( + ({ eventId, noteId }: { eventId: string; noteId: string }) => + addNoteToEvent!({ id, eventId, noteId }), + [id] + ); - const onColumnResized: OnColumnResized = ({ columnId, delta }) => - applyDeltaToColumnWidth!({ id, columnId, delta }); + const onColumnSorted: OnColumnSorted = useCallback( + sorted => { + updateSort!({ id, sort: sorted }); + }, + [id] + ); - const onPinEvent: OnPinEvent = eventId => pinEvent!({ id, eventId }); + const onColumnRemoved: OnColumnRemoved = useCallback( + columnId => removeColumn!({ id, columnId }), + [id] + ); - const onUnPinEvent: OnUnPinEvent = eventId => unPinEvent!({ id, eventId }); + const onColumnResized: OnColumnResized = useCallback( + ({ columnId, delta }) => applyDeltaToColumnWidth!({ id, columnId, delta }), + [id] + ); + + const onPinEvent: OnPinEvent = useCallback(eventId => pinEvent!({ id, eventId }), [id]); - const onUpdateNote: UpdateNote = (note: Note) => updateNote!({ note }); + const onUnPinEvent: OnUnPinEvent = useCallback(eventId => unPinEvent!({ id, eventId }), [id]); - const onUpdateColumns: OnUpdateColumns = columns => updateColumns!({ id, columns }); + const onUpdateNote: UpdateNote = useCallback((note: Note) => updateNote!({ note }), []); + + const onUpdateColumns: OnUpdateColumns = useCallback( + columns => updateColumns!({ id, columns }), + [id] + ); return ( ( prevProps.columnHeaders === nextProps.columnHeaders && prevProps.data === nextProps.data && prevProps.eventIdToNoteIds === nextProps.eventIdToNoteIds && - prevProps.getNotesByIds === nextProps.getNotesByIds && + prevProps.notesById === nextProps.notesById && prevProps.height === nextProps.height && prevProps.id === nextProps.id && prevProps.isEventViewer === nextProps.isEventViewer && @@ -194,7 +208,7 @@ const makeMapStateToProps = () => { return { columnHeaders: memoizedColumnHeaders(columns, browserFields), eventIdToNoteIds, - getNotesByIds: getNotesByIds(state), + notesById: getNotesByIds(state), id, pinnedEventIds, }; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider_item_badge.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider_item_badge.tsx index 98cf0a78b1d1f..79f9c32a176f5 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider_item_badge.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider_item_badge.tsx @@ -5,7 +5,7 @@ */ import { noop } from 'lodash/fp'; -import React, { useState } from 'react'; +import React, { useCallback, useState } from 'react'; import { BrowserFields } from '../../../containers/source'; @@ -51,23 +51,23 @@ export const ProviderItemBadge = React.memo( }) => { const [isPopoverOpen, setIsPopoverOpen] = useState(false); - function togglePopover() { + const togglePopover = useCallback(() => { setIsPopoverOpen(!isPopoverOpen); - } + }, [isPopoverOpen]); - function closePopover() { + const closePopover = useCallback(() => { setIsPopoverOpen(false); - } + }, []); - function onToggleEnabledProvider() { + const onToggleEnabledProvider = useCallback(() => { toggleEnabledProvider(); closePopover(); - } + }, [toggleEnabledProvider]); - function onToggleExcludedProvider() { + const onToggleExcludedProvider = useCallback(() => { toggleExcludedProvider(); closePopover(); - } + }, [toggleExcludedProvider]); return ( diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/footer/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/footer/index.tsx index c1772d9e55577..a0942cbaba091 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/footer/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/footer/index.tsx @@ -18,7 +18,7 @@ import { EuiToolTip, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import React, { useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { pure } from 'recompose'; import styled from 'styled-components'; @@ -182,14 +182,14 @@ export const Footer = React.memo( const [paginationLoading, setPaginationLoading] = useState(false); const [updatedAt, setUpdatedAt] = useState(null); - const loadMore = () => { + const loadMore = useCallback(() => { setPaginationLoading(true); onLoadMore(nextCursor, tieBreaker); - }; + }, [nextCursor, tieBreaker, onLoadMore]); - const onButtonClick = () => setIsPopoverOpen(!isPopoverOpen); + const onButtonClick = useCallback(() => setIsPopoverOpen(!isPopoverOpen), [isPopoverOpen]); - const closePopover = () => setIsPopoverOpen(false); + const closePopover = useCallback(() => setIsPopoverOpen(false), []); useEffect(() => { if (paginationLoading && !isLoading) { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/helpers.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/helpers.test.tsx index 855c951fbcecc..b30771760bad3 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/helpers.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/helpers.test.tsx @@ -156,7 +156,7 @@ describe('Combined Queries', () => { }) ).toEqual({ filterQuery: - '{"bool":{"must":[],"filter":[{"bool":{"filter":[{"bool":{"should":[{"range":{"@timestamp":{"gte":1521830963132,"time_zone":"America/New_York"}}}],"minimum_should_match":1}},{"bool":{"should":[{"range":{"@timestamp":{"lte":1521862432253,"time_zone":"America/New_York"}}}],"minimum_should_match":1}}]}}],"should":[],"must_not":[]}}', + '{"bool":{"must":[],"filter":[{"bool":{"filter":[{"bool":{"should":[{"range":{"@timestamp":{"gte":1521830963132}}}],"minimum_should_match":1}},{"bool":{"should":[{"range":{"@timestamp":{"lte":1521862432253}}}],"minimum_should_match":1}}]}}],"should":[],"must_not":[]}}', }); }); @@ -174,7 +174,7 @@ describe('Combined Queries', () => { end: endDate, })!; expect(filterQuery).toEqual( - '{"bool":{"must":[],"filter":[{"bool":{"filter":[{"bool":{"should":[{"match_phrase":{"name":"Provider 1"}}],"minimum_should_match":1}},{"bool":{"filter":[{"bool":{"should":[{"range":{"@timestamp":{"gte":1521830963132,"time_zone":"America/New_York"}}}],"minimum_should_match":1}},{"bool":{"should":[{"range":{"@timestamp":{"lte":1521862432253,"time_zone":"America/New_York"}}}],"minimum_should_match":1}}]}}]}}],"should":[],"must_not":[]}}' + '{"bool":{"must":[],"filter":[{"bool":{"filter":[{"bool":{"should":[{"match_phrase":{"name":"Provider 1"}}],"minimum_should_match":1}},{"bool":{"filter":[{"bool":{"should":[{"range":{"@timestamp":{"gte":1521830963132}}}],"minimum_should_match":1}},{"bool":{"should":[{"range":{"@timestamp":{"lte":1521862432253}}}],"minimum_should_match":1}}]}}]}}],"should":[],"must_not":[]}}' ); }); @@ -194,7 +194,7 @@ describe('Combined Queries', () => { end: endDate, })!; expect(filterQuery).toEqual( - '{"bool":{"must":[],"filter":[{"bool":{"filter":[{"bool":{"should":[{"range":{"@timestamp":{"gte":1521848183232,"lte":1521848183232,"time_zone":"America/New_York"}}}],"minimum_should_match":1}},{"bool":{"filter":[{"bool":{"should":[{"range":{"@timestamp":{"gte":1521830963132,"time_zone":"America/New_York"}}}],"minimum_should_match":1}},{"bool":{"should":[{"range":{"@timestamp":{"lte":1521862432253,"time_zone":"America/New_York"}}}],"minimum_should_match":1}}]}}]}}],"should":[],"must_not":[]}}' + '{"bool":{"must":[],"filter":[{"bool":{"filter":[{"bool":{"should":[{"range":{"@timestamp":{"gte":1521848183232,"lte":1521848183232}}}],"minimum_should_match":1}},{"bool":{"filter":[{"bool":{"should":[{"range":{"@timestamp":{"gte":1521830963132}}}],"minimum_should_match":1}},{"bool":{"should":[{"range":{"@timestamp":{"lte":1521862432253}}}],"minimum_should_match":1}}]}}]}}],"should":[],"must_not":[]}}' ); }); @@ -214,7 +214,7 @@ describe('Combined Queries', () => { end: endDate, })!; expect(filterQuery).toEqual( - '{"bool":{"must":[],"filter":[{"bool":{"filter":[{"bool":{"should":[{"range":{"@timestamp":{"gte":1521848183232,"lte":1521848183232,"time_zone":"America/New_York"}}}],"minimum_should_match":1}},{"bool":{"filter":[{"bool":{"should":[{"range":{"@timestamp":{"gte":1521830963132,"time_zone":"America/New_York"}}}],"minimum_should_match":1}},{"bool":{"should":[{"range":{"@timestamp":{"lte":1521862432253,"time_zone":"America/New_York"}}}],"minimum_should_match":1}}]}}]}}],"should":[],"must_not":[]}}' + '{"bool":{"must":[],"filter":[{"bool":{"filter":[{"bool":{"should":[{"range":{"@timestamp":{"gte":1521848183232,"lte":1521848183232}}}],"minimum_should_match":1}},{"bool":{"filter":[{"bool":{"should":[{"range":{"@timestamp":{"gte":1521830963132}}}],"minimum_should_match":1}},{"bool":{"should":[{"range":{"@timestamp":{"lte":1521862432253}}}],"minimum_should_match":1}}]}}]}}],"should":[],"must_not":[]}}' ); }); @@ -234,7 +234,7 @@ describe('Combined Queries', () => { end: endDate, })!; expect(filterQuery).toEqual( - '{"bool":{"must":[],"filter":[{"bool":{"filter":[{"bool":{"should":[{"match":{"event.end":1521848183232}}],"minimum_should_match":1}},{"bool":{"filter":[{"bool":{"should":[{"range":{"@timestamp":{"gte":1521830963132,"time_zone":"America/New_York"}}}],"minimum_should_match":1}},{"bool":{"should":[{"range":{"@timestamp":{"lte":1521862432253,"time_zone":"America/New_York"}}}],"minimum_should_match":1}}]}}]}}],"should":[],"must_not":[]}}' + '{"bool":{"must":[],"filter":[{"bool":{"filter":[{"bool":{"should":[{"match":{"event.end":1521848183232}}],"minimum_should_match":1}},{"bool":{"filter":[{"bool":{"should":[{"range":{"@timestamp":{"gte":1521830963132}}}],"minimum_should_match":1}},{"bool":{"should":[{"range":{"@timestamp":{"lte":1521862432253}}}],"minimum_should_match":1}}]}}]}}],"should":[],"must_not":[]}}' ); }); @@ -254,7 +254,7 @@ describe('Combined Queries', () => { end: endDate, })!; expect(filterQuery).toEqual( - '{"bool":{"must":[],"filter":[{"bool":{"filter":[{"bool":{"should":[{"match":{"event.end":1521848183232}}],"minimum_should_match":1}},{"bool":{"filter":[{"bool":{"should":[{"range":{"@timestamp":{"gte":1521830963132,"time_zone":"America/New_York"}}}],"minimum_should_match":1}},{"bool":{"should":[{"range":{"@timestamp":{"lte":1521862432253,"time_zone":"America/New_York"}}}],"minimum_should_match":1}}]}}]}}],"should":[],"must_not":[]}}' + '{"bool":{"must":[],"filter":[{"bool":{"filter":[{"bool":{"should":[{"match":{"event.end":1521848183232}}],"minimum_should_match":1}},{"bool":{"filter":[{"bool":{"should":[{"range":{"@timestamp":{"gte":1521830963132}}}],"minimum_should_match":1}},{"bool":{"should":[{"range":{"@timestamp":{"lte":1521862432253}}}],"minimum_should_match":1}}]}}]}}],"should":[],"must_not":[]}}' ); }); @@ -271,7 +271,7 @@ describe('Combined Queries', () => { end: endDate, })!; expect(filterQuery).toEqual( - '{"bool":{"must":[],"filter":[{"bool":{"filter":[{"bool":{"should":[{"match_phrase":{"host.name":"host-1"}}],"minimum_should_match":1}},{"bool":{"filter":[{"bool":{"should":[{"range":{"@timestamp":{"gte":1521830963132,"time_zone":"America/New_York"}}}],"minimum_should_match":1}},{"bool":{"should":[{"range":{"@timestamp":{"lte":1521862432253,"time_zone":"America/New_York"}}}],"minimum_should_match":1}}]}}]}}],"should":[],"must_not":[]}}' + '{"bool":{"must":[],"filter":[{"bool":{"filter":[{"bool":{"should":[{"match_phrase":{"host.name":"host-1"}}],"minimum_should_match":1}},{"bool":{"filter":[{"bool":{"should":[{"range":{"@timestamp":{"gte":1521830963132}}}],"minimum_should_match":1}},{"bool":{"should":[{"range":{"@timestamp":{"lte":1521862432253}}}],"minimum_should_match":1}}]}}]}}],"should":[],"must_not":[]}}' ); }); @@ -289,7 +289,7 @@ describe('Combined Queries', () => { end: endDate, })!; expect(filterQuery).toEqual( - '{"bool":{"must":[],"filter":[{"bool":{"filter":[{"bool":{"should":[{"bool":{"should":[{"match_phrase":{"name":"Provider 1"}}],"minimum_should_match":1}},{"bool":{"should":[{"match_phrase":{"host.name":"host-1"}}],"minimum_should_match":1}}],"minimum_should_match":1}},{"bool":{"filter":[{"bool":{"should":[{"range":{"@timestamp":{"gte":1521830963132,"time_zone":"America/New_York"}}}],"minimum_should_match":1}},{"bool":{"should":[{"range":{"@timestamp":{"lte":1521862432253,"time_zone":"America/New_York"}}}],"minimum_should_match":1}}]}}]}}],"should":[],"must_not":[]}}' + '{"bool":{"must":[],"filter":[{"bool":{"filter":[{"bool":{"should":[{"bool":{"should":[{"match_phrase":{"name":"Provider 1"}}],"minimum_should_match":1}},{"bool":{"should":[{"match_phrase":{"host.name":"host-1"}}],"minimum_should_match":1}}],"minimum_should_match":1}},{"bool":{"filter":[{"bool":{"should":[{"range":{"@timestamp":{"gte":1521830963132}}}],"minimum_should_match":1}},{"bool":{"should":[{"range":{"@timestamp":{"lte":1521862432253}}}],"minimum_should_match":1}}]}}]}}],"should":[],"must_not":[]}}' ); }); @@ -307,7 +307,7 @@ describe('Combined Queries', () => { end: endDate, })!; expect(filterQuery).toEqual( - '{"bool":{"must":[],"filter":[{"bool":{"filter":[{"bool":{"filter":[{"bool":{"should":[{"match_phrase":{"name":"Provider 1"}}],"minimum_should_match":1}},{"bool":{"should":[{"match_phrase":{"host.name":"host-1"}}],"minimum_should_match":1}}]}},{"bool":{"filter":[{"bool":{"should":[{"range":{"@timestamp":{"gte":1521830963132,"time_zone":"America/New_York"}}}],"minimum_should_match":1}},{"bool":{"should":[{"range":{"@timestamp":{"lte":1521862432253,"time_zone":"America/New_York"}}}],"minimum_should_match":1}}]}}]}}],"should":[],"must_not":[]}}' + '{"bool":{"must":[],"filter":[{"bool":{"filter":[{"bool":{"filter":[{"bool":{"should":[{"match_phrase":{"name":"Provider 1"}}],"minimum_should_match":1}},{"bool":{"should":[{"match_phrase":{"host.name":"host-1"}}],"minimum_should_match":1}}]}},{"bool":{"filter":[{"bool":{"should":[{"range":{"@timestamp":{"gte":1521830963132}}}],"minimum_should_match":1}},{"bool":{"should":[{"range":{"@timestamp":{"lte":1521862432253}}}],"minimum_should_match":1}}]}}]}}],"should":[],"must_not":[]}}' ); }); @@ -327,7 +327,7 @@ describe('Combined Queries', () => { end: endDate, })!; expect(filterQuery).toEqual( - '{"bool":{"must":[],"filter":[{"bool":{"filter":[{"bool":{"should":[{"bool":{"should":[{"bool":{"filter":[{"bool":{"should":[{"match_phrase":{"name":"Provider 1"}}],"minimum_should_match":1}},{"bool":{"filter":[{"bool":{"should":[{"match_phrase":{"name":"Provider 3"}}],"minimum_should_match":1}},{"bool":{"should":[{"match_phrase":{"name":"Provider 4"}}],"minimum_should_match":1}}]}}]}},{"bool":{"filter":[{"bool":{"should":[{"match_phrase":{"name":"Provider 2"}}],"minimum_should_match":1}},{"bool":{"should":[{"match_phrase":{"name":"Provider 5"}}],"minimum_should_match":1}}]}}],"minimum_should_match":1}},{"bool":{"should":[{"match_phrase":{"host.name":"host-1"}}],"minimum_should_match":1}}],"minimum_should_match":1}},{"bool":{"filter":[{"bool":{"should":[{"range":{"@timestamp":{"gte":1521830963132,"time_zone":"America/New_York"}}}],"minimum_should_match":1}},{"bool":{"should":[{"range":{"@timestamp":{"lte":1521862432253,"time_zone":"America/New_York"}}}],"minimum_should_match":1}}]}}]}}],"should":[],"must_not":[]}}' + '{"bool":{"must":[],"filter":[{"bool":{"filter":[{"bool":{"should":[{"bool":{"should":[{"bool":{"filter":[{"bool":{"should":[{"match_phrase":{"name":"Provider 1"}}],"minimum_should_match":1}},{"bool":{"filter":[{"bool":{"should":[{"match_phrase":{"name":"Provider 3"}}],"minimum_should_match":1}},{"bool":{"should":[{"match_phrase":{"name":"Provider 4"}}],"minimum_should_match":1}}]}}]}},{"bool":{"filter":[{"bool":{"should":[{"match_phrase":{"name":"Provider 2"}}],"minimum_should_match":1}},{"bool":{"should":[{"match_phrase":{"name":"Provider 5"}}],"minimum_should_match":1}}]}}],"minimum_should_match":1}},{"bool":{"should":[{"match_phrase":{"host.name":"host-1"}}],"minimum_should_match":1}}],"minimum_should_match":1}},{"bool":{"filter":[{"bool":{"should":[{"range":{"@timestamp":{"gte":1521830963132}}}],"minimum_should_match":1}},{"bool":{"should":[{"range":{"@timestamp":{"lte":1521862432253}}}],"minimum_should_match":1}}]}}]}}],"should":[],"must_not":[]}}' ); }); @@ -347,7 +347,7 @@ describe('Combined Queries', () => { end: endDate, })!; expect(filterQuery).toEqual( - '{"bool":{"must":[],"filter":[{"bool":{"filter":[{"bool":{"filter":[{"bool":{"should":[{"bool":{"filter":[{"bool":{"should":[{"match_phrase":{"name":"Provider 1"}}],"minimum_should_match":1}},{"bool":{"filter":[{"bool":{"should":[{"match_phrase":{"name":"Provider 3"}}],"minimum_should_match":1}},{"bool":{"should":[{"match_phrase":{"name":"Provider 4"}}],"minimum_should_match":1}}]}}]}},{"bool":{"filter":[{"bool":{"should":[{"match_phrase":{"name":"Provider 2"}}],"minimum_should_match":1}},{"bool":{"should":[{"match_phrase":{"name":"Provider 5"}}],"minimum_should_match":1}}]}}],"minimum_should_match":1}},{"bool":{"should":[{"match_phrase":{"host.name":"host-1"}}],"minimum_should_match":1}}]}},{"bool":{"filter":[{"bool":{"should":[{"range":{"@timestamp":{"gte":1521830963132,"time_zone":"America/New_York"}}}],"minimum_should_match":1}},{"bool":{"should":[{"range":{"@timestamp":{"lte":1521862432253,"time_zone":"America/New_York"}}}],"minimum_should_match":1}}]}}]}}],"should":[],"must_not":[]}}' + '{"bool":{"must":[],"filter":[{"bool":{"filter":[{"bool":{"filter":[{"bool":{"should":[{"bool":{"filter":[{"bool":{"should":[{"match_phrase":{"name":"Provider 1"}}],"minimum_should_match":1}},{"bool":{"filter":[{"bool":{"should":[{"match_phrase":{"name":"Provider 3"}}],"minimum_should_match":1}},{"bool":{"should":[{"match_phrase":{"name":"Provider 4"}}],"minimum_should_match":1}}]}}]}},{"bool":{"filter":[{"bool":{"should":[{"match_phrase":{"name":"Provider 2"}}],"minimum_should_match":1}},{"bool":{"should":[{"match_phrase":{"name":"Provider 5"}}],"minimum_should_match":1}}]}}],"minimum_should_match":1}},{"bool":{"should":[{"match_phrase":{"host.name":"host-1"}}],"minimum_should_match":1}}]}},{"bool":{"filter":[{"bool":{"should":[{"range":{"@timestamp":{"gte":1521830963132}}}],"minimum_should_match":1}},{"bool":{"should":[{"range":{"@timestamp":{"lte":1521862432253}}}],"minimum_should_match":1}}]}}]}}],"should":[],"must_not":[]}}' ); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/index.tsx index ab92f22a4c89f..78a9488b2fdbb 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/index.tsx @@ -5,7 +5,7 @@ */ import { isEqual } from 'lodash/fp'; -import React, { useEffect } from 'react'; +import React, { useEffect, useCallback } from 'react'; import { connect } from 'react-redux'; import { ActionCreator } from 'typescript-fsa'; @@ -159,79 +159,84 @@ const StatefulTimelineComponent = React.memo( updateItemsPerPage, upsertColumn, }) => { - const onDataProviderRemoved: OnDataProviderRemoved = ( - providerId: string, - andProviderId?: string - ) => removeProvider!({ id, providerId, andProviderId }); + const onDataProviderRemoved: OnDataProviderRemoved = useCallback( + (providerId: string, andProviderId?: string) => + removeProvider!({ id, providerId, andProviderId }), + [id] + ); - const onToggleDataProviderEnabled: OnToggleDataProviderEnabled = ({ - providerId, - enabled, - andProviderId, - }) => - updateDataProviderEnabled!({ - id, - enabled, - providerId, - andProviderId, - }); + const onToggleDataProviderEnabled: OnToggleDataProviderEnabled = useCallback( + ({ providerId, enabled, andProviderId }) => + updateDataProviderEnabled!({ + id, + enabled, + providerId, + andProviderId, + }), + [id] + ); - const onToggleDataProviderExcluded: OnToggleDataProviderExcluded = ({ - providerId, - excluded, - andProviderId, - }) => - updateDataProviderExcluded!({ - id, - excluded, - providerId, - andProviderId, - }); + const onToggleDataProviderExcluded: OnToggleDataProviderExcluded = useCallback( + ({ providerId, excluded, andProviderId }) => + updateDataProviderExcluded!({ + id, + excluded, + providerId, + andProviderId, + }), + [id] + ); - const onDataProviderEditedLocal: OnDataProviderEdited = ({ - andProviderId, - excluded, - field, - operator, - providerId, - value, - }) => - onDataProviderEdited!({ - andProviderId, - excluded, - field, - id, - operator, - providerId, - value, - }); - const onChangeDataProviderKqlQuery: OnChangeDataProviderKqlQuery = ({ providerId, kqlQuery }) => - updateDataProviderKqlQuery!({ id, kqlQuery, providerId }); + const onDataProviderEditedLocal: OnDataProviderEdited = useCallback( + ({ andProviderId, excluded, field, operator, providerId, value }) => + onDataProviderEdited!({ + andProviderId, + excluded, + field, + id, + operator, + providerId, + value, + }), + [id] + ); - const onChangeItemsPerPage: OnChangeItemsPerPage = itemsChangedPerPage => - updateItemsPerPage!({ id, itemsPerPage: itemsChangedPerPage }); + const onChangeDataProviderKqlQuery: OnChangeDataProviderKqlQuery = useCallback( + ({ providerId, kqlQuery }) => updateDataProviderKqlQuery!({ id, kqlQuery, providerId }), + [id] + ); - const onChangeDroppableAndProvider: OnChangeDroppableAndProvider = providerId => - updateHighlightedDropAndProviderId!({ id, providerId }); + const onChangeItemsPerPage: OnChangeItemsPerPage = useCallback( + itemsChangedPerPage => updateItemsPerPage!({ id, itemsPerPage: itemsChangedPerPage }), + [id] + ); - const toggleColumn = (column: ColumnHeader) => { - const exists = columns.findIndex(c => c.id === column.id) !== -1; + const onChangeDroppableAndProvider: OnChangeDroppableAndProvider = useCallback( + providerId => updateHighlightedDropAndProviderId!({ id, providerId }), + [id] + ); - if (!exists && upsertColumn != null) { - upsertColumn({ - column, - id, - index: 1, - }); - } + const toggleColumn = useCallback( + (column: ColumnHeader) => { + const exists = columns.findIndex(c => c.id === column.id) !== -1; - if (exists && removeColumn != null) { - removeColumn({ - columnId: column.id, - id, - }); - } - }; + if (!exists && upsertColumn != null) { + upsertColumn({ + column, + id, + index: 1, + }); + } + + if (exists && removeColumn != null) { + removeColumn({ + columnId: column.id, + id, + }); + } + }, + [columns, id] + ); useEffect(() => { if (createTimeline != null) { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/properties/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/properties/index.tsx index b983963c34f55..111e31479932a 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/properties/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/properties/index.tsx @@ -5,7 +5,7 @@ */ import { EuiAvatar, EuiFlexItem, EuiIcon } from '@elastic/eui'; -import React, { useState } from 'react'; +import React, { useState, useCallback } from 'react'; import styled, { injectGlobal } from 'styled-components'; import { Note } from '../../../lib/note'; @@ -114,17 +114,17 @@ export const Properties = React.memo( const [showActions, setShowActions] = useState(false); const [showNotes, setShowNotes] = useState(false); - const onButtonClick = () => { + const onButtonClick = useCallback(() => { setShowActions(!showActions); - }; + }, [showActions]); - const onToggleShowNotes = () => { + const onToggleShowNotes = useCallback(() => { setShowNotes(!showNotes); - }; + }, [showNotes]); - const onClosePopover = () => { + const onClosePopover = useCallback(() => { setShowActions(false); - }; + }, []); const datePickerWidth = width - diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/search_or_filter/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/search_or_filter/index.tsx index 91113a545821d..0ebceccfa90c5 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/search_or_filter/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/search_or_filter/index.tsx @@ -5,7 +5,7 @@ */ import { getOr } from 'lodash/fp'; -import * as React from 'react'; +import React, { useCallback } from 'react'; import { connect } from 'react-redux'; import { ActionCreator } from 'typescript-fsa'; import { StaticIndexPattern } from 'ui/index_patterns'; @@ -56,26 +56,32 @@ const StatefulSearchOrFilterComponent = React.memo( timelineId, updateKqlMode, }) => { - const applyFilterQueryFromKueryExpression = (expression: string) => - applyKqlFilterQuery({ - id: timelineId, - filterQuery: { - kuery: { + const applyFilterQueryFromKueryExpression = useCallback( + (expression: string) => + applyKqlFilterQuery({ + id: timelineId, + filterQuery: { + kuery: { + kind: 'kuery', + expression, + }, + serializedQuery: convertKueryToElasticSearchQuery(expression, indexPattern), + }, + }), + [indexPattern, timelineId] + ); + + const setFilterQueryDraftFromKueryExpression = useCallback( + (expression: string) => + setKqlFilterQueryDraft({ + id: timelineId, + filterQueryDraft: { kind: 'kuery', expression, }, - serializedQuery: convertKueryToElasticSearchQuery(expression, indexPattern), - }, - }); - - const setFilterQueryDraftFromKueryExpression = (expression: string) => - setKqlFilterQueryDraft({ - id: timelineId, - filterQueryDraft: { - kind: 'kuery', - expression, - }, - }); + }), + [timelineId] + ); return ( ( ({ alwaysShow = false, hoverContent, render }) => { const [showHoverContent, setShowHoverContent] = useState(false); - function onMouseEnter() { + const onMouseEnter = useCallback(() => { setShowHoverContent(true); - } + }, []); - function onMouseLeave() { + const onMouseLeave = useCallback(() => { setShowHoverContent(false); - } + }, []); + return ( <>{render(showHoverContent)} diff --git a/x-pack/legacy/plugins/siem/public/containers/authentications/authentications_over_time/authentications_over_time.gql_query.ts b/x-pack/legacy/plugins/siem/public/containers/authentications/authentications_over_time/authentications_over_time.gql_query.ts new file mode 100644 index 0000000000000..dc05f4f8232d5 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/authentications/authentications_over_time/authentications_over_time.gql_query.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 gql from 'graphql-tag'; + +export const AuthenticationsOverTimeGqlQuery = gql` + query GetAuthenticationsOverTimeQuery( + $sourceId: ID! + $timerange: TimerangeInput! + $defaultIndex: [String!]! + $filterQuery: String + $inspect: Boolean! + ) { + source(id: $sourceId) { + id + AuthenticationsOverTime( + timerange: $timerange + filterQuery: $filterQuery + defaultIndex: $defaultIndex + ) { + authenticationsOverTime { + x + y + g + } + totalCount + inspect @include(if: $inspect) { + dsl + response + } + } + } + } +`; diff --git a/x-pack/legacy/plugins/siem/public/containers/authentications/authentications_over_time/index.tsx b/x-pack/legacy/plugins/siem/public/containers/authentications/authentications_over_time/index.tsx new file mode 100644 index 0000000000000..1d6d96869b6a9 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/authentications/authentications_over_time/index.tsx @@ -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 { getOr } from 'lodash/fp'; +import React from 'react'; +import { Query } from 'react-apollo'; +import { connect } from 'react-redux'; + +import chrome from 'ui/chrome'; +import { DEFAULT_INDEX_KEY } from '../../../../common/constants'; +import { inputsModel, State, inputsSelectors, hostsModel } from '../../../store'; +import { createFilter, getDefaultFetchPolicy } from '../../helpers'; +import { QueryTemplate, QueryTemplateProps } from '../../query_template'; + +import { AuthenticationsOverTimeGqlQuery } from './authentications_over_time.gql_query'; +import { + GetAuthenticationsOverTimeQuery, + MatrixOverTimeHistogramData, +} from '../../../graphql/types'; + +const ID = 'authenticationsOverTimeQuery'; + +export interface AuthenticationsArgs { + endDate: number; + authenticationsOverTime: MatrixOverTimeHistogramData[]; + id: string; + inspect: inputsModel.InspectQuery; + loading: boolean; + refetch: inputsModel.Refetch; + startDate: number; + totalCount: number; +} + +export interface OwnProps extends QueryTemplateProps { + children?: (args: AuthenticationsArgs) => React.ReactNode; + type: hostsModel.HostsType; +} + +export interface AuthenticationsOverTimeComponentReduxProps { + isInspected: boolean; +} + +type AuthenticationsOverTimeProps = OwnProps & AuthenticationsOverTimeComponentReduxProps; + +class AuthenticationsOverTimeComponentQuery extends QueryTemplate< + AuthenticationsOverTimeProps, + GetAuthenticationsOverTimeQuery.Query, + GetAuthenticationsOverTimeQuery.Variables +> { + public render() { + const { + children, + filterQuery, + id = ID, + isInspected, + sourceId, + startDate, + endDate, + } = this.props; + return ( + + query={AuthenticationsOverTimeGqlQuery} + fetchPolicy={getDefaultFetchPolicy()} + notifyOnNetworkStatusChange + variables={{ + filterQuery: createFilter(filterQuery), + sourceId, + timerange: { + interval: '12h', + from: startDate!, + to: endDate!, + }, + defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY), + inspect: isInspected, + }} + > + {({ data, loading, refetch }) => { + const source = getOr({}, `source.AuthenticationsOverTime`, data); + const authenticationsOverTime = getOr([], `authenticationsOverTime`, source); + const totalCount = getOr(-1, 'totalCount', source); + return children!({ + endDate: endDate!, + authenticationsOverTime, + id, + inspect: getOr(null, 'inspect', source), + loading, + refetch, + startDate: startDate!, + totalCount, + }); + }} + + ); + } +} + +const makeMapStateToProps = () => { + const getQuery = inputsSelectors.globalQueryByIdSelector(); + const mapStateToProps = (state: State, { type, id = ID }: OwnProps) => { + const { isInspected } = getQuery(state, id); + return { + isInspected, + }; + }; + return mapStateToProps; +}; + +export const AuthenticationsOverTimeQuery = connect(makeMapStateToProps)( + AuthenticationsOverTimeComponentQuery +); diff --git a/x-pack/legacy/plugins/siem/public/containers/events/events_over_time/index.tsx b/x-pack/legacy/plugins/siem/public/containers/events/events_over_time/index.tsx index 6fde630c691b4..77ce98e180ab0 100644 --- a/x-pack/legacy/plugins/siem/public/containers/events/events_over_time/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/events/events_over_time/index.tsx @@ -56,7 +56,6 @@ class EventsOverTimeComponentQuery extends QueryTemplate< isInspected, sourceId, startDate, - timezone, } = this.props; return ( @@ -70,7 +69,6 @@ class EventsOverTimeComponentQuery extends QueryTemplate< interval: '12h', from: startDate!, to: endDate!, - timezone, }, defaultIndex: chrome.getUiSettingsClient().get(DEFAULT_INDEX_KEY), inspect: isInspected, diff --git a/x-pack/legacy/plugins/siem/public/containers/events/last_event_time/mock.ts b/x-pack/legacy/plugins/siem/public/containers/events/last_event_time/mock.ts index 09cdfbd07bffd..ca8786077851f 100644 --- a/x-pack/legacy/plugins/siem/public/containers/events/last_event_time/mock.ts +++ b/x-pack/legacy/plugins/siem/public/containers/events/last_event_time/mock.ts @@ -28,11 +28,11 @@ interface MockLastEventTimeQuery { }; } -const getTimeTwelveDaysAgo = () => { +const getTimeTwelveMinutesAgo = () => { const d = new Date(); const ts = d.getTime(); - const twelveDays = ts - 12 * 24 * 60 * 60 * 1000; - return new Date(twelveDays).toISOString(); + const twelveMinutes = ts - 12 * 60 * 1000; + return new Date(twelveMinutes).toISOString(); }; export const mockLastEventTimeQuery: MockLastEventTimeQuery[] = [ @@ -51,7 +51,7 @@ export const mockLastEventTimeQuery: MockLastEventTimeQuery[] = [ source: { id: 'default', LastEventTime: { - lastSeen: getTimeTwelveDaysAgo(), + lastSeen: getTimeTwelveMinutesAgo(), errorMessage: null, }, }, diff --git a/x-pack/legacy/plugins/siem/public/containers/query_template.tsx b/x-pack/legacy/plugins/siem/public/containers/query_template.tsx index 035ebba5ae6ba..b51eac492c48a 100644 --- a/x-pack/legacy/plugins/siem/public/containers/query_template.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/query_template.tsx @@ -17,7 +17,6 @@ export interface QueryTemplateProps { skip?: boolean; sourceId: string; startDate?: number; - timezone?: string; } // eslint-disable-next-line @typescript-eslint/no-explicit-any type FetchMoreOptionsArgs = FetchMoreQueryOptions & diff --git a/x-pack/legacy/plugins/siem/public/containers/source/index.tsx b/x-pack/legacy/plugins/siem/public/containers/source/index.tsx index edd5740f62879..ff6e5e4d0c788 100644 --- a/x-pack/legacy/plugins/siem/public/containers/source/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/source/index.tsx @@ -57,30 +57,28 @@ interface WithSourceProps { sourceId: string; } -export const WithSource = React.memo(({ children, sourceId }) => { - const getIndexFields = (title: string, fields: IndexField[]): StaticIndexPattern => +const getIndexFields = memoizeOne( + (title: string, fields: IndexField[]): StaticIndexPattern => fields && fields.length > 0 ? { fields: fields.map(field => pick(['name', 'searchable', 'type', 'aggregatable'], field)), title, } - : { fields: [], title }; + : { fields: [], title } +); - const getBrowserFields = (fields: IndexField[]): BrowserFields => +const getBrowserFields = memoizeOne( + (fields: IndexField[]): BrowserFields => fields && fields.length > 0 ? fields.reduce( (accumulator: BrowserFields, field: IndexField) => set([field.category, 'fields', field.name], field, accumulator), {} ) - : {}; - const getBrowserFieldsMemo: (fields: IndexField[]) => BrowserFields = memoizeOne( - getBrowserFields - ); - const getIndexFieldsMemo: ( - title: string, - fields: IndexField[] - ) => StaticIndexPattern = memoizeOne(getIndexFields); + : {} +); + +export const WithSource = React.memo(({ children, sourceId }) => { return ( query={sourceQuery} @@ -94,8 +92,8 @@ export const WithSource = React.memo(({ children, sourceId }) = {({ data }) => children({ indicesExist: get('source.status.indicesExist', data), - browserFields: getBrowserFieldsMemo(get('source.status.indexFields', data)), - indexPattern: getIndexFieldsMemo( + browserFields: getBrowserFields(get('source.status.indexFields', data)), + indexPattern: getIndexFields( chrome .getUiSettingsClient() .get(DEFAULT_INDEX_KEY) diff --git a/x-pack/legacy/plugins/siem/public/containers/timeline/all/index.tsx b/x-pack/legacy/plugins/siem/public/containers/timeline/all/index.tsx index c3bff998fdefd..5ff28480f1b3f 100644 --- a/x-pack/legacy/plugins/siem/public/containers/timeline/all/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/timeline/all/index.tsx @@ -36,43 +36,43 @@ interface OwnProps extends AllTimelinesVariables { children?: (args: AllTimelinesArgs) => React.ReactNode; } -const getAllTimeline = (variables: string, timelines: TimelineResult[]): OpenTimelineResult[] => - timelines.map(timeline => ({ - created: timeline.created, - description: timeline.description, - eventIdToNoteIds: - timeline.eventIdToNoteIds != null - ? timeline.eventIdToNoteIds.reduce((acc, note) => { - if (note.eventId != null) { - const notes = getOr([], note.eventId, acc); - return { ...acc, [note.eventId]: [...notes, note.noteId] }; - } - return acc; - }, {}) - : null, - favorite: timeline.favorite, - noteIds: timeline.noteIds, - notes: - timeline.notes != null - ? timeline.notes.map(note => ({ ...note, savedObjectId: note.noteId })) - : null, - pinnedEventIds: - timeline.pinnedEventIds != null - ? timeline.pinnedEventIds.reduce( - (acc, pinnedEventId) => ({ ...acc, [pinnedEventId]: true }), - {} - ) - : null, - savedObjectId: timeline.savedObjectId, - title: timeline.title, - updated: timeline.updated, - updatedBy: timeline.updatedBy, - })); +const getAllTimeline = memoizeOne( + (variables: string, timelines: TimelineResult[]): OpenTimelineResult[] => + timelines.map(timeline => ({ + created: timeline.created, + description: timeline.description, + eventIdToNoteIds: + timeline.eventIdToNoteIds != null + ? timeline.eventIdToNoteIds.reduce((acc, note) => { + if (note.eventId != null) { + const notes = getOr([], note.eventId, acc); + return { ...acc, [note.eventId]: [...notes, note.noteId] }; + } + return acc; + }, {}) + : null, + favorite: timeline.favorite, + noteIds: timeline.noteIds, + notes: + timeline.notes != null + ? timeline.notes.map(note => ({ ...note, savedObjectId: note.noteId })) + : null, + pinnedEventIds: + timeline.pinnedEventIds != null + ? timeline.pinnedEventIds.reduce( + (acc, pinnedEventId) => ({ ...acc, [pinnedEventId]: true }), + {} + ) + : null, + savedObjectId: timeline.savedObjectId, + title: timeline.title, + updated: timeline.updated, + updatedBy: timeline.updatedBy, + })) +); export const AllTimelinesQuery = React.memo( ({ children, onlyUserFavorite, pageInfo, search, sort }) => { - const memoizedAllTimeline = memoizeOne(getAllTimeline); - const variables: GetAllTimeline.Variables = { onlyUserFavorite, pageInfo, @@ -90,7 +90,7 @@ export const AllTimelinesQuery = React.memo( return children!({ loading, totalCount: getOr(0, 'getAllTimeline.totalCount', data), - timelines: memoizedAllTimeline( + timelines: getAllTimeline( JSON.stringify(variables), getOr([], 'getAllTimeline.timeline', data) ), diff --git a/x-pack/legacy/plugins/siem/public/containers/timeline/details/index.tsx b/x-pack/legacy/plugins/siem/public/containers/timeline/details/index.tsx index 54dd44063f5da..cfb3f8bd8dc77 100644 --- a/x-pack/legacy/plugins/siem/public/containers/timeline/details/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/timeline/details/index.tsx @@ -28,11 +28,12 @@ export interface TimelineDetailsProps { sourceId: string; } +const getDetailsEvent = memoizeOne( + (variables: string, detail: DetailItem[]): DetailItem[] => detail +); + export const TimelineDetailsComponentQuery = React.memo( ({ children, indexName, eventId, executeQuery, sourceId }) => { - const getDetailsEvent = (variables: string, detail: DetailItem[]): DetailItem[] => detail; - const getDetailsEventMemo = memoizeOne(getDetailsEvent); - const variables: GetTimelineDetailsQuery.Variables = { sourceId, indexName, @@ -49,7 +50,7 @@ export const TimelineDetailsComponentQuery = React.memo( {({ data, loading, refetch }) => { return children!({ loading, - detailsData: getDetailsEventMemo( + detailsData: getDetailsEvent( JSON.stringify(variables), getOr([], 'source.TimelineDetails.data', data) ), diff --git a/x-pack/legacy/plugins/siem/public/graphql/introspection.json b/x-pack/legacy/plugins/siem/public/graphql/introspection.json index cc9438d67bd26..488a6310db3f2 100644 --- a/x-pack/legacy/plugins/siem/public/graphql/introspection.json +++ b/x-pack/legacy/plugins/siem/public/graphql/introspection.json @@ -727,6 +727,53 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "AuthenticationsOverTime", + "description": "", + "args": [ + { + "name": "timerange", + "description": "", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "INPUT_OBJECT", "name": "TimerangeInput", "ofType": null } + }, + "defaultValue": null + }, + { + "name": "filterQuery", + "description": "", + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null + }, + { + "name": "defaultIndex", + "description": "", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + } + } + }, + "defaultValue": null + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "AuthenticationsOverTimeData", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "Timeline", "description": "", @@ -2351,12 +2398,6 @@ "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } }, "defaultValue": null - }, - { - "name": "timezone", - "description": "The default browser set time zone", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null } ], "interfaces": null, @@ -3123,6 +3164,108 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "OBJECT", + "name": "AuthenticationsOverTimeData", + "description": "", + "fields": [ + { + "name": "inspect", + "description": "", + "args": [], + "type": { "kind": "OBJECT", "name": "Inspect", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "authenticationsOverTime", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "MatrixOverTimeHistogramData", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "totalCount", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "MatrixOverTimeHistogramData", + "description": "", + "fields": [ + { + "name": "x", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "y", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "g", + "description": "", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, { "kind": "INPUT_OBJECT", "name": "PaginationInput", @@ -5842,53 +5985,6 @@ "enumValues": null, "possibleTypes": null }, - { - "kind": "OBJECT", - "name": "MatrixOverTimeHistogramData", - "description": "", - "fields": [ - { - "name": "x", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "y", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "g", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, { "kind": "INPUT_OBJECT", "name": "HostsSortField", diff --git a/x-pack/legacy/plugins/siem/public/graphql/types.ts b/x-pack/legacy/plugins/siem/public/graphql/types.ts index 332592a64dfa5..dbb62d73e5994 100644 --- a/x-pack/legacy/plugins/siem/public/graphql/types.ts +++ b/x-pack/legacy/plugins/siem/public/graphql/types.ts @@ -27,8 +27,6 @@ export interface TimerangeInput { to: number; /** The beginning of the timerange */ from: number; - /** The default browser set time zone */ - timezone?: Maybe; } export interface PaginationInputPaginated { @@ -403,6 +401,8 @@ export interface Source { /** Gets Authentication success and failures based on a timerange */ Authentications: AuthenticationsData; + AuthenticationsOverTime: AuthenticationsOverTimeData; + Timeline: TimelineData; TimelineDetails: TimelineDetailsData; @@ -636,6 +636,22 @@ export interface Inspect { response: string[]; } +export interface AuthenticationsOverTimeData { + inspect?: Maybe; + + authenticationsOverTime: MatrixOverTimeHistogramData[]; + + totalCount: number; +} + +export interface MatrixOverTimeHistogramData { + x: number; + + y: number; + + g: string; +} + export interface TimelineData { edges: TimelineEdges[]; @@ -1220,14 +1236,6 @@ export interface EventsOverTimeData { totalCount: number; } -export interface MatrixOverTimeHistogramData { - x: number; - - y: number; - - g: string; -} - export interface HostsData { edges: HostsEdges[]; @@ -1980,6 +1988,13 @@ export interface AuthenticationsSourceArgs { defaultIndex: string[]; } +export interface AuthenticationsOverTimeSourceArgs { + timerange: TimerangeInput; + + filterQuery?: Maybe; + + defaultIndex: string[]; +} export interface TimelineSourceArgs { pagination: PaginationInput; @@ -2243,6 +2258,58 @@ export interface DeleteTimelineMutationArgs { // Documents // ==================================================== +export namespace GetAuthenticationsOverTimeQuery { + export type Variables = { + sourceId: string; + timerange: TimerangeInput; + defaultIndex: string[]; + filterQuery?: Maybe; + inspect: boolean; + }; + + export type Query = { + __typename?: 'Query'; + + source: Source; + }; + + export type Source = { + __typename?: 'Source'; + + id: string; + + AuthenticationsOverTime: AuthenticationsOverTime; + }; + + export type AuthenticationsOverTime = { + __typename?: 'AuthenticationsOverTimeData'; + + authenticationsOverTime: _AuthenticationsOverTime[]; + + totalCount: number; + + inspect: Maybe; + }; + + export type _AuthenticationsOverTime = { + __typename?: 'MatrixOverTimeHistogramData'; + + x: number; + + y: number; + + g: string; + }; + + export type Inspect = { + __typename?: 'Inspect'; + + dsl: string[]; + + response: string[]; + }; +} + export namespace GetAuthenticationsQuery { export type Variables = { sourceId: string; diff --git a/x-pack/legacy/plugins/siem/public/lib/keury/index.ts b/x-pack/legacy/plugins/siem/public/lib/keury/index.ts index 0c78fb9d6f45f..7bd8560a1770a 100644 --- a/x-pack/legacy/plugins/siem/public/lib/keury/index.ts +++ b/x-pack/legacy/plugins/siem/public/lib/keury/index.ts @@ -87,7 +87,10 @@ export const convertToBuildEsQuery = ({ }) => { try { return JSON.stringify( - buildEsQuery(indexPattern, queries, filters.filter(f => f.meta.disabled === false), config) + buildEsQuery(indexPattern, queries, filters.filter(f => f.meta.disabled === false), { + ...config, + dateFormatTZ: null, + }) ); } catch (exp) { return ''; diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/details/__snapshots__/body.test.tsx.snap b/x-pack/legacy/plugins/siem/public/pages/hosts/details/__snapshots__/body.test.tsx.snap deleted file mode 100644 index 3815b319820ef..0000000000000 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/details/__snapshots__/body.test.tsx.snap +++ /dev/null @@ -1,15 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`body render snapshot 1`] = ` - - - - - -`; diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/details/body.test.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/details/body.test.tsx deleted file mode 100644 index 83af0a616a660..0000000000000 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/details/body.test.tsx +++ /dev/null @@ -1,99 +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 { shallow, mount } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import React from 'react'; -import { StaticIndexPattern } from 'ui/index_patterns'; - -import { mockIndexPattern } from '../../../mock/index_pattern'; -import { TestProviders } from '../../../mock/test_providers'; -import { mockUiSettings } from '../../../mock/ui_settings'; -import { CommonChildren } from '../navigation/types'; -import { HostDetailsBody } from './body'; -import { useKibanaCore } from '../../../lib/compose/kibana_core'; - -const mockUseKibanaCore = useKibanaCore as jest.Mock; -jest.mock('../../../lib/compose/kibana_core'); -mockUseKibanaCore.mockImplementation(() => ({ - uiSettings: mockUiSettings, -})); - -jest.mock('../../../containers/source', () => ({ - indicesExistOrDataTemporarilyUnavailable: () => true, - WithSource: ({ - children, - }: { - children: (args: { - indicesExist: boolean; - indexPattern: StaticIndexPattern; - }) => React.ReactNode; - }) => children({ indicesExist: true, indexPattern: mockIndexPattern }), -})); - -describe('body', () => { - test('render snapshot', () => { - const child: CommonChildren = () => {'I am a child'}; - const wrapper = shallow( - - {}} - to={0} - /> - - ); - expect(toJson(wrapper)).toMatchSnapshot(); - }); - - test('it should pass expected object properties to children', () => { - const child = jest.fn(); - mount( - - {}} - to={0} - /> - - ); - // match against everything but the functions to ensure they are there as expected - expect(child.mock.calls[0][0]).toMatchObject({ - endDate: 0, - filterQuery: - '{"bool":{"must":[],"filter":[{"match_all":{}},{"match_phrase":{"host.name":{"query":"host-1"}}}],"should":[],"must_not":[]}}', - skip: false, - startDate: 0, - type: 'details', - indexPattern: { - fields: [ - { name: '@timestamp', searchable: true, type: 'date', aggregatable: true }, - { name: '@version', searchable: true, type: 'string', aggregatable: true }, - { name: 'agent.ephemeral_id', searchable: true, type: 'string', aggregatable: true }, - { name: 'agent.hostname', searchable: true, type: 'string', aggregatable: true }, - { name: 'agent.id', searchable: true, type: 'string', aggregatable: true }, - { name: 'agent.test1', searchable: true, type: 'string', aggregatable: true }, - { name: 'agent.test2', searchable: true, type: 'string', aggregatable: true }, - { name: 'agent.test3', searchable: true, type: 'string', aggregatable: true }, - { name: 'agent.test4', searchable: true, type: 'string', aggregatable: true }, - { name: 'agent.test5', searchable: true, type: 'string', aggregatable: true }, - { name: 'agent.test6', searchable: true, type: 'string', aggregatable: true }, - { name: 'agent.test7', searchable: true, type: 'string', aggregatable: true }, - { name: 'agent.test8', searchable: true, type: 'string', aggregatable: true }, - { name: 'host.name', searchable: true, type: 'string', aggregatable: true }, - ], - title: 'filebeat-*,auditbeat-*,packetbeat-*', - }, - hostName: 'host-1', - }); - }); -}); diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/details/body.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/details/body.tsx deleted file mode 100644 index ae8ebcf41cd56..0000000000000 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/details/body.tsx +++ /dev/null @@ -1,106 +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 { getEsQueryConfig } from '@kbn/es-query'; -import React from 'react'; -import { connect } from 'react-redux'; - -import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../../containers/source'; -import { setAbsoluteRangeDatePicker as dispatchAbsoluteRangeDatePicker } from '../../../store/inputs/actions'; -import { scoreIntervalToDateTime } from '../../../components/ml/score/score_interval_to_datetime'; -import { Anomaly } from '../../../components/ml/types'; -import { convertToBuildEsQuery } from '../../../lib/keury'; -import { useKibanaCore } from '../../../lib/compose/kibana_core'; - -import { HostDetailsBodyComponentProps } from './types'; -import { type, makeMapStateToProps } from './utils'; - -const HostDetailsBodyComponent = React.memo( - ({ - children, - deleteQuery, - detailName, - filters, - from, - isInitializing, - query, - setAbsoluteRangeDatePicker, - setQuery, - to, - }) => { - const core = useKibanaCore(); - return ( - - {({ indicesExist, indexPattern }) => { - const filterQuery = convertToBuildEsQuery({ - config: getEsQueryConfig(core.uiSettings), - indexPattern, - queries: [query], - filters: [ - { - meta: { - alias: null, - negate: false, - disabled: false, - type: 'phrase', - key: 'host.name', - value: detailName, - params: { - query: detailName, - }, - }, - query: { - match: { - 'host.name': { - query: detailName, - type: 'phrase', - }, - }, - }, - }, - ...filters, - ], - }); - return indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( - <> - {children({ - deleteQuery, - endDate: to, - filterQuery, - skip: isInitializing, - setQuery, - startDate: from, - type, - indexPattern, - hostName: detailName, - narrowDateRange: (score: Anomaly, interval: string) => { - const fromTo = scoreIntervalToDateTime(score, interval); - setAbsoluteRangeDatePicker({ - id: 'global', - from: fromTo.from, - to: fromTo.to, - }); - }, - updateDateRange: (min: number, max: number) => { - setAbsoluteRangeDatePicker({ id: 'global', from: min, to: max }); - }, - })} - - ) : null; - }} - - ); - } -); - -HostDetailsBodyComponent.displayName = 'HostDetailsBodyComponent'; - -export const HostDetailsBody = connect( - makeMapStateToProps, - { - setAbsoluteRangeDatePicker: dispatchAbsoluteRangeDatePicker, - } -)(HostDetailsBodyComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/details/details_tabs.test.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/details/details_tabs.test.tsx new file mode 100644 index 0000000000000..6ceebc1708b18 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/details/details_tabs.test.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 { mount } from 'enzyme'; +import React from 'react'; +import { StaticIndexPattern } from 'ui/index_patterns'; +import { MemoryRouter } from 'react-router-dom'; + +import { mockIndexPattern } from '../../../mock/index_pattern'; +import { TestProviders } from '../../../mock/test_providers'; +import { mockUiSettings } from '../../../mock/ui_settings'; +import { HostDetailsTabs } from './details_tabs'; +import { SetAbsoluteRangeDatePicker } from './types'; +import { hostDetailsPagePath } from '../types'; +import { type } from './utils'; +import { useKibanaCore } from '../../../lib/compose/kibana_core'; + +jest.mock('../../../lib/settings/use_kibana_ui_setting'); + +const mockUseKibanaCore = useKibanaCore as jest.Mock; +jest.mock('../../../lib/compose/kibana_core'); +mockUseKibanaCore.mockImplementation(() => ({ + uiSettings: mockUiSettings, +})); + +jest.mock('../../../containers/source', () => ({ + indicesExistOrDataTemporarilyUnavailable: () => true, + WithSource: ({ + children, + }: { + children: (args: { + indicesExist: boolean; + indexPattern: StaticIndexPattern; + }) => React.ReactNode; + }) => children({ indicesExist: true, indexPattern: mockIndexPattern }), +})); + +// Test will fail because we will to need to mock some core services to make the test work +// For now let's forget about SiemSearchBar +jest.mock('../../../components/search_bar', () => ({ + SiemSearchBar: () => null, +})); + +describe('body', () => { + const scenariosMap = { + authentications: 'AuthenticationsQueryTabBody', + allHosts: 'HostsQueryTabBody', + uncommonProcesses: 'UncommonProcessQueryTabBody', + anomalies: 'AnomaliesQueryTabBody', + events: 'EventsQueryTabBody', + }; + + Object.entries(scenariosMap).forEach(([path, componentName]) => + test(`it should pass expected object properties to ${componentName}`, () => { + const wrapper = mount( + + + {}} + to={0} + setAbsoluteRangeDatePicker={(jest.fn() as unknown) as SetAbsoluteRangeDatePicker} + hostDetailsPagePath={hostDetailsPagePath} + indexPattern={mockIndexPattern} + type={type} + filterQuery='{"bool":{"must":[],"filter":[{"match_all":{}},{"match_phrase":{"host.name":{"query":"host-1"}}}],"should":[],"must_not":[]}}' + /> + + + ); + + // match against everything but the functions to ensure they are there as expected + expect(wrapper.find(componentName).props()).toMatchObject({ + endDate: 0, + filterQuery: + '{"bool":{"must":[],"filter":[{"match_all":{}},{"match_phrase":{"host.name":{"query":"host-1"}}}],"should":[],"must_not":[]}}', + skip: false, + startDate: 0, + type: 'details', + indexPattern: { + fields: [ + { name: '@timestamp', searchable: true, type: 'date', aggregatable: true }, + { name: '@version', searchable: true, type: 'string', aggregatable: true }, + { name: 'agent.ephemeral_id', searchable: true, type: 'string', aggregatable: true }, + { name: 'agent.hostname', searchable: true, type: 'string', aggregatable: true }, + { name: 'agent.id', searchable: true, type: 'string', aggregatable: true }, + { name: 'agent.test1', searchable: true, type: 'string', aggregatable: true }, + { name: 'agent.test2', searchable: true, type: 'string', aggregatable: true }, + { name: 'agent.test3', searchable: true, type: 'string', aggregatable: true }, + { name: 'agent.test4', searchable: true, type: 'string', aggregatable: true }, + { name: 'agent.test5', searchable: true, type: 'string', aggregatable: true }, + { name: 'agent.test6', searchable: true, type: 'string', aggregatable: true }, + { name: 'agent.test7', searchable: true, type: 'string', aggregatable: true }, + { name: 'agent.test8', searchable: true, type: 'string', aggregatable: true }, + { name: 'host.name', searchable: true, type: 'string', aggregatable: true }, + ], + title: 'filebeat-*,auditbeat-*,packetbeat-*', + }, + hostName: 'host-1', + }); + }) + ); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/details/details_tabs.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/details/details_tabs.tsx new file mode 100644 index 0000000000000..48b6d34d0b28b --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/details/details_tabs.tsx @@ -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 React, { useCallback } from 'react'; +import { Route, Switch } from 'react-router-dom'; + +import { scoreIntervalToDateTime } from '../../../components/ml/score/score_interval_to_datetime'; +import { Anomaly } from '../../../components/ml/types'; +import { HostsTableType } from '../../../store/hosts/model'; + +import { HostDetailsTabsProps } from './types'; +import { type } from './utils'; + +import { + HostsQueryTabBody, + AuthenticationsQueryTabBody, + UncommonProcessQueryTabBody, + AnomaliesQueryTabBody, + EventsQueryTabBody, +} from '../navigation'; + +const HostDetailsTabs = React.memo( + ({ + deleteQuery, + filterQuery, + from, + isInitializing, + detailName, + setAbsoluteRangeDatePicker, + setQuery, + to, + indexPattern, + hostDetailsPagePath, + }) => { + const narrowDateRange = useCallback( + (score: Anomaly, interval: string) => { + const fromTo = scoreIntervalToDateTime(score, interval); + setAbsoluteRangeDatePicker({ + id: 'global', + from: fromTo.from, + to: fromTo.to, + }); + }, + [setAbsoluteRangeDatePicker, scoreIntervalToDateTime] + ); + + const updateDateRange = useCallback( + (min: number, max: number) => { + setAbsoluteRangeDatePicker({ id: 'global', from: min, to: max }); + }, + [setAbsoluteRangeDatePicker, scoreIntervalToDateTime] + ); + + const tabProps = { + deleteQuery, + endDate: to, + filterQuery, + skip: isInitializing, + setQuery, + startDate: from, + type, + indexPattern, + hostName: detailName, + narrowDateRange, + updateDateRange, + }; + + return ( + + } + /> + } + /> + } + /> + } + /> + } + /> + + ); + } +); + +HostDetailsTabs.displayName = 'HostDetailsTabs'; + +export { HostDetailsTabs }; diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/details/index.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/details/index.tsx index d1d29c3d2ea82..30744b3e24c4b 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/details/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/details/index.tsx @@ -11,6 +11,8 @@ import { compose } from 'redux'; import { connect } from 'react-redux'; import { StickyContainer } from 'react-sticky'; +import { inputsSelectors, State } from '../../../store'; + import { FiltersGlobal } from '../../../components/filters_global'; import { HeaderPage } from '../../../components/header_page'; import { KpiHostDetailsQuery } from '../../../containers/kpi_host_details'; @@ -32,22 +34,19 @@ import { LastEventIndexKey } from '../../../graphql/types'; import { convertToBuildEsQuery } from '../../../lib/keury'; import { setAbsoluteRangeDatePicker as dispatchAbsoluteRangeDatePicker } from '../../../store/inputs/actions'; import { SpyRoute } from '../../../utils/route/spy_routes'; +import { useKibanaCore } from '../../../lib/compose/kibana_core'; -import { HostsQueryProps } from '../hosts'; import { HostsEmptyPage } from '../hosts_empty_page'; - -export { HostDetailsBody } from './body'; import { navTabsHostDetails } from './nav_tabs'; -import { HostDetailsComponentProps } from './types'; -import { makeMapStateToProps } from './utils'; -import { useKibanaCore } from '../../../lib/compose/kibana_core'; +import { HostDetailsComponentProps, HostDetailsProps } from './types'; +import { HostDetailsTabs } from './details_tabs'; +import { type } from './utils'; const HostOverviewManage = manageQuery(HostOverview); const KpiHostDetailsManage = manageQuery(KpiHostsComponent); const HostDetailsComponent = React.memo( ({ - detailName, filters, from, isInitializing, @@ -56,6 +55,9 @@ const HostDetailsComponent = React.memo( setHostDetailsTablesActivePageToZero, setQuery, to, + detailName, + deleteQuery, + hostDetailsPagePath, }) => { useEffect(() => { setHostDetailsTablesActivePageToZero(null); @@ -96,94 +98,114 @@ const HostDetailsComponent = React.memo( ], }); return indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( - - - - + <> + + + + - - } - title={detailName} - /> - - {({ hostOverview, loading, id, inspect, refetch }) => ( - - {({ isLoadingAnomaliesData, anomaliesData }) => ( - { - const fromTo = scoreIntervalToDateTime(score, interval); - setAbsoluteRangeDatePicker({ - id: 'global', - from: fromTo.from, - to: fromTo.to, - }); - }} - /> - )} - - )} - + + } + title={detailName} + /> + + {({ hostOverview, loading, id, inspect, refetch }) => ( + + {({ isLoadingAnomaliesData, anomaliesData }) => ( + { + const fromTo = scoreIntervalToDateTime(score, interval); + setAbsoluteRangeDatePicker({ + id: 'global', + from: fromTo.from, + to: fromTo.to, + }); + }} + /> + )} + + )} + - + - - {({ kpiHostDetails, id, inspect, loading, refetch }) => ( - { - setAbsoluteRangeDatePicker({ id: 'global', from: min, to: max }); - }} - /> - )} - + + {({ kpiHostDetails, id, inspect, loading, refetch }) => ( + { + setAbsoluteRangeDatePicker({ id: 'global', from: min, to: max }); + }} + /> + )} + + + - + + + + - - + ) : ( <> - ); @@ -197,7 +219,16 @@ const HostDetailsComponent = React.memo( HostDetailsComponent.displayName = 'HostDetailsComponent'; -export const HostDetails = compose>( +export const makeMapStateToProps = () => { + const getGlobalQuerySelector = inputsSelectors.globalQuerySelector(); + const getGlobalFiltersQuerySelector = inputsSelectors.globalFiltersQuerySelector(); + return (state: State) => ({ + query: getGlobalQuerySelector(state), + filters: getGlobalFiltersQuerySelector(state), + }); +}; + +export const HostDetails = compose>( connect( makeMapStateToProps, { diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/details/types.ts b/x-pack/legacy/plugins/siem/public/pages/hosts/details/types.ts index b4dda2cee8760..9df57970176eb 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/details/types.ts +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/details/types.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { StaticIndexPattern } from 'ui/index_patterns'; import { Filter } from '@kbn/es-query'; import { ActionCreator } from 'typescript-fsa'; import { Query } from 'src/plugins/data/common'; @@ -11,13 +12,10 @@ import { Query } from 'src/plugins/data/common'; import { InputsModelId } from '../../../store/inputs/constants'; import { HostComponentProps } from '../../../components/link_to/redirect_to_hosts'; import { HostsTableType } from '../../../store/hosts/model'; -import { HostsQueryProps } from '../hosts'; +import { HostsQueryProps } from '../types'; import { NavTab } from '../../../components/navigation/types'; -import { - AnomaliesChildren, - CommonChildren, - KeyHostsNavTabWithoutMlPermission, -} from '../navigation/types'; +import { KeyHostsNavTabWithoutMlPermission } from '../navigation/types'; +import { hostsModel } from '../../../store'; interface HostDetailsComponentReduxProps { query: Query; @@ -31,14 +29,16 @@ interface HostBodyComponentDispatchProps { to: number; }>; detailName: string; + hostDetailsPagePath: string; } interface HostDetailsComponentDispatchProps extends HostBodyComponentDispatchProps { setHostDetailsTablesActivePageToZero: ActionCreator; } -export interface HostDetailsBodyProps extends HostsQueryProps { - children: CommonChildren | AnomaliesChildren; +export interface HostDetailsProps extends HostsQueryProps { + detailName: string; + hostDetailsPagePath: string; } export type HostDetailsComponentProps = HostDetailsComponentReduxProps & @@ -46,10 +46,6 @@ export type HostDetailsComponentProps = HostDetailsComponentReduxProps & HostComponentProps & HostsQueryProps; -export type HostDetailsBodyComponentProps = HostDetailsComponentReduxProps & - HostBodyComponentDispatchProps & - HostDetailsBodyProps; - type KeyHostDetailsNavTabWithoutMlPermission = HostsTableType.authentications & HostsTableType.uncommonProcesses & HostsTableType.events; @@ -62,3 +58,16 @@ type KeyHostDetailsNavTab = | KeyHostDetailsNavTabWithMlPermission; export type HostDetailsNavTab = Record; + +export type HostDetailsTabsProps = HostBodyComponentDispatchProps & + HostsQueryProps & { + indexPattern: StaticIndexPattern; + type: hostsModel.HostsType; + filterQuery: string; + }; + +export type SetAbsoluteRangeDatePicker = ActionCreator<{ + id: InputsModelId; + from: number; + to: number; +}>; diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/details/utils.ts b/x-pack/legacy/plugins/siem/public/pages/hosts/details/utils.ts index 52efe93c0c8dc..7483636cfe03d 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/details/utils.ts +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/details/utils.ts @@ -6,7 +6,7 @@ import { Breadcrumb } from 'ui/chrome'; -import { hostsModel, inputsSelectors, State } from '../../../store'; +import { hostsModel } from '../../../store'; import { HostsTableType } from '../../../store/hosts/model'; import { getHostsUrl, getHostDetailsUrl } from '../../../components/link_to/redirect_to_hosts'; @@ -15,15 +15,6 @@ import { RouteSpyState } from '../../../utils/route/types'; export const type = hostsModel.HostsType.details; -export const makeMapStateToProps = () => { - const getGlobalQuerySelector = inputsSelectors.globalQuerySelector(); - const getGlobalFiltersQuerySelector = inputsSelectors.globalFiltersQuerySelector(); - return (state: State) => ({ - query: getGlobalQuerySelector(state), - filters: getGlobalFiltersQuerySelector(state), - }); -}; - const TabNameMappedToI18nKey = { [HostsTableType.hosts]: i18n.NAVIGATION_ALL_HOSTS_TITLE, [HostsTableType.authentications]: i18n.NAVIGATION_AUTHENTICATIONS_TITLE, diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.test.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.test.tsx index 5b6444148045d..d2c9822889c26 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.test.tsx @@ -19,7 +19,8 @@ import { wait } from '../../lib/helpers'; import { TestProviders } from '../../mock'; import { mockUiSettings } from '../../mock/ui_settings'; import { InputsModelId } from '../../store/inputs/constants'; -import { Hosts, HostsComponentProps } from './hosts'; +import { HostsComponentProps } from './types'; +import { Hosts } from './hosts'; import { useKibanaCore } from '../../lib/compose/kibana_core'; jest.mock('../../lib/settings/use_kibana_ui_setting'); @@ -97,6 +98,7 @@ describe('Hosts - rendering', () => { }>, query: { query: '', language: 'kuery' }, filters: [], + hostsPagePath: '', }; beforeAll(() => { diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.tsx index fc969974609ea..334d730378b23 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.tsx @@ -5,13 +5,11 @@ */ import { EuiSpacer } from '@elastic/eui'; -import { Filter, getEsQueryConfig } from '@kbn/es-query'; +import { getEsQueryConfig } from '@kbn/es-query'; import * as React from 'react'; import { compose } from 'redux'; import { connect } from 'react-redux'; import { StickyContainer } from 'react-sticky'; -import { ActionCreator } from 'typescript-fsa'; -import { Query } from 'src/plugins/data/common'; import { FiltersGlobal } from '../../components/filters_global'; import { GlobalTimeArgs } from '../../containers/global_time'; @@ -27,41 +25,34 @@ import { SiemSearchBar } from '../../components/search_bar'; import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../containers/source'; import { LastEventIndexKey } from '../../graphql/types'; import { convertToBuildEsQuery } from '../../lib/keury'; -import { inputsSelectors, State } from '../../store'; +import { inputsSelectors, State, hostsModel } from '../../store'; import { setAbsoluteRangeDatePicker as dispatchSetAbsoluteRangeDatePicker } from '../../store/inputs/actions'; -import { InputsModelId } from '../../store/inputs/constants'; import { SpyRoute } from '../../utils/route/spy_routes'; +import { useKibanaCore } from '../../lib/compose/kibana_core'; import { HostsEmptyPage } from './hosts_empty_page'; import { navTabsHosts } from './nav_tabs'; import * as i18n from './translations'; -import { useKibanaCore } from '../../lib/compose/kibana_core'; +import { HostsComponentProps, HostsComponentReduxProps } from './types'; +import { HostsTabs } from './hosts_tabs'; const KpiHostsComponentManage = manageQuery(KpiHostsComponent); -interface HostsComponentReduxProps { - query: Query; - filters: Filter[]; -} - -interface HostsComponentDispatchProps { - setAbsoluteRangeDatePicker: ActionCreator<{ - id: InputsModelId; - from: number; - to: number; - }>; -} - -export type HostsQueryProps = { timezone?: string } & GlobalTimeArgs; - -export type HostsComponentProps = HostsComponentReduxProps & - HostsComponentDispatchProps & - HostsQueryProps; - const HostsComponent = React.memo( - ({ isInitializing, filters, from, query, setAbsoluteRangeDatePicker, setQuery, to }) => { + ({ + deleteQuery, + isInitializing, + filters, + from, + query, + setAbsoluteRangeDatePicker, + setQuery, + to, + hostsPagePath, + }) => { const capabilities = React.useContext(MlCapabilitiesContext); const core = useKibanaCore(); + return ( <> @@ -73,48 +64,62 @@ const HostsComponent = React.memo( filters, }); return indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( - - - - + <> + + + + - } - title={i18n.PAGE_TITLE} - /> - <> - - {({ kpiHosts, loading, id, inspect, refetch }) => ( - { - setAbsoluteRangeDatePicker({ id: 'global', from: min, to: max }); - }} - /> - )} - - - } + title={i18n.PAGE_TITLE} /> - - - + <> + + {({ kpiHosts, loading, id, inspect, refetch }) => ( + { + setAbsoluteRangeDatePicker({ id: 'global', from: min, to: max }); + }} + /> + )} + + + + + + + + ) : ( <> @@ -142,8 +147,12 @@ const makeMapStateToProps = () => { return mapStateToProps; }; +interface HostsProps extends GlobalTimeArgs { + hostsPagePath: string; +} + // eslint-disable-next-line @typescript-eslint/no-explicit-any -export const Hosts = compose>( +export const Hosts = compose>( connect( makeMapStateToProps, { diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/hosts_body.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/hosts_body.tsx deleted file mode 100644 index 242c66bb3a9ee..0000000000000 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/hosts_body.tsx +++ /dev/null @@ -1,98 +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 { getEsQueryConfig } from '@kbn/es-query'; -import React, { memo } from 'react'; -import { connect } from 'react-redux'; - -import { scoreIntervalToDateTime } from '../../components/ml/score/score_interval_to_datetime'; -import { Anomaly } from '../../components/ml/types'; -import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../containers/source'; -import { convertToBuildEsQuery } from '../../lib/keury'; -import { useKibanaCore } from '../../lib/compose/kibana_core'; -import { hostsModel, inputsSelectors, State } from '../../store'; -import { setAbsoluteRangeDatePicker as dispatchSetAbsoluteRangeDatePicker } from '../../store/inputs/actions'; - -import { HostsComponentProps } from './hosts'; -import { CommonChildren, AnomaliesChildren } from './navigation/types'; - -interface HostsBodyComponentProps extends HostsComponentProps { - children: CommonChildren | AnomaliesChildren; -} - -const HostsBodyComponent = memo( - ({ - children, - deleteQuery, - filters, - from, - isInitializing, - query, - setAbsoluteRangeDatePicker, - setQuery, - timezone, - to, - }) => { - const core = useKibanaCore(); - return ( - - {({ indicesExist, indexPattern }) => { - const filterQuery = convertToBuildEsQuery({ - config: getEsQueryConfig(core.uiSettings), - indexPattern, - queries: [query], - filters, - }); - return indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( - <> - {children({ - deleteQuery, - endDate: to, - filterQuery, - skip: isInitializing, - setQuery, - startDate: from, - timezone, - type: hostsModel.HostsType.page, - indexPattern, - narrowDateRange: (score: Anomaly, interval: string) => { - const fromTo = scoreIntervalToDateTime(score, interval); - setAbsoluteRangeDatePicker({ - id: 'global', - from: fromTo.from, - to: fromTo.to, - }); - }, - updateDateRange: (min: number, max: number) => { - setAbsoluteRangeDatePicker({ id: 'global', from: min, to: max }); - }, - })} - - ) : null; - }} - - ); - } -); - -HostsBodyComponent.displayName = 'HostsBodyComponent'; - -const makeMapStateToProps = () => { - const getGlobalQuerySelector = inputsSelectors.globalQuerySelector(); - const getGlobalFiltersQuerySelector = inputsSelectors.globalFiltersQuerySelector(); - const mapStateToProps = (state: State) => ({ - query: getGlobalQuerySelector(state), - filters: getGlobalFiltersQuerySelector(state), - }); - return mapStateToProps; -}; - -export const HostsBody = connect( - makeMapStateToProps, - { - setAbsoluteRangeDatePicker: dispatchSetAbsoluteRangeDatePicker, - } -)(HostsBodyComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/hosts_tabs.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/hosts_tabs.tsx new file mode 100644 index 0000000000000..6dbfb422ed7a6 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/hosts_tabs.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, { memo } from 'react'; +import { Route, Switch } from 'react-router-dom'; + +import { HostsTabsProps } from './types'; +import { scoreIntervalToDateTime } from '../../components/ml/score/score_interval_to_datetime'; +import { Anomaly } from '../../components/ml/types'; +import { HostsTableType } from '../../store/hosts/model'; + +import { + HostsQueryTabBody, + AuthenticationsQueryTabBody, + UncommonProcessQueryTabBody, + AnomaliesQueryTabBody, + EventsQueryTabBody, +} from './navigation'; + +const HostsTabs = memo( + ({ + deleteQuery, + filterQuery, + setAbsoluteRangeDatePicker, + to, + from, + setQuery, + isInitializing, + type, + indexPattern, + hostsPagePath, + }) => { + const tabProps = { + deleteQuery, + endDate: to, + filterQuery, + skip: isInitializing, + setQuery, + startDate: from, + type, + indexPattern, + narrowDateRange: (score: Anomaly, interval: string) => { + const fromTo = scoreIntervalToDateTime(score, interval); + setAbsoluteRangeDatePicker({ + id: 'global', + from: fromTo.from, + to: fromTo.to, + }); + }, + updateDateRange: (min: number, max: number) => { + setAbsoluteRangeDatePicker({ id: 'global', from: min, to: max }); + }, + }; + + return ( + + } + /> + } + /> + } + /> + } + /> + } + /> + + ); + } +); + +HostsTabs.displayName = 'HostsTabs'; + +export { HostsTabs }; diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/index.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/index.tsx index 840c65af8229e..c8d450a62cc57 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/index.tsx @@ -7,23 +7,13 @@ import React from 'react'; import { Redirect, Route, Switch, RouteComponentProps } from 'react-router-dom'; -import { HostDetailsBody, HostDetails } from './details'; -import { - HostsQueryTabBody, - AuthenticationsQueryTabBody, - UncommonProcessQueryTabBody, - AnomaliesQueryTabBody, - EventsQueryTabBody, -} from './navigation'; -import { HostsBody } from './hosts_body'; +import { HostDetails } from './details'; import { HostsTableType } from '../../store/hosts/model'; + import { GlobalTime } from '../../containers/global_time'; import { SiemPageName } from '../home/types'; import { Hosts } from './hosts'; -import { useKibanaUiSetting } from '../../lib/settings/use_kibana_ui_setting'; -import { DEFAULT_TIMEZONE_BROWSER } from '../../../common/constants'; - -const hostsPagePath = `/:pageName(${SiemPageName.hosts})`; +import { hostsPagePath, hostDetailsPagePath } from './types'; const getHostsTabPath = (pagePath: string) => `${pagePath}/:tabName(` + @@ -34,7 +24,7 @@ const getHostsTabPath = (pagePath: string) => `${HostsTableType.events})`; const getHostDetailsTabPath = (pagePath: string) => - `${pagePath}/:detailName/:tabName(` + + `${hostDetailsPagePath}/:tabName(` + `${HostsTableType.authentications}|` + `${HostsTableType.uncommonProcesses}|` + `${HostsTableType.anomalies}|` + @@ -42,223 +32,58 @@ const getHostDetailsTabPath = (pagePath: string) => type Props = Partial> & { url: string }; -export const HostsContainer = React.memo(({ url }) => { - const [timezone] = useKibanaUiSetting(DEFAULT_TIMEZONE_BROWSER); - return ( - - {({ to, from, setQuery, deleteQuery, isInitializing }) => ( - - ( - ( - <> - - - - )} - /> - )} - /> - ( - <> - - ( - - )} - /> - ( - - )} - /> - ( - - )} - /> - ( - - )} - /> - ( - - )} - /> - - )} - /> - ( - <> - - ( - - )} - /> - ( - - )} - /> - ( - - )} - /> - ( - - )} - /> - ( - - )} - /> - - )} - /> - ( - - )} - /> - ( - - )} - /> - - )} - - ); -}); +export const HostsContainer = React.memo(({ url }) => ( + + {({ to, from, setQuery, deleteQuery, isInitializing }) => ( + + ( + + )} + /> + ( + + )} + /> + } + /> + ( + + )} + /> + + )} + +)); HostsContainer.displayName = 'HostsContainer'; diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/authentications_query_tab_body.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/authentications_query_tab_body.tsx index baac539e14cfc..7a0a25ef18842 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/authentications_query_tab_body.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/authentications_query_tab_body.tsx @@ -6,12 +6,17 @@ import { getOr } from 'lodash/fp'; import React from 'react'; +import { EuiSpacer } from '@elastic/eui'; import { AuthenticationTable } from '../../../components/page/hosts/authentications_table'; import { manageQuery } from '../../../components/page/manage_query'; +import { AuthenticationsOverTimeHistogram } from '../../../components/page/hosts/authentications_over_time'; +import { AuthenticationsOverTimeQuery } from '../../../containers/authentications/authentications_over_time'; import { AuthenticationsQuery } from '../../../containers/authentications'; import { HostsComponentsQueryProps } from './types'; +import { hostsModel } from '../../../store/hosts'; const AuthenticationTableManage = manageQuery(AuthenticationTable); +const AuthenticationsOverTimeManage = manageQuery(AuthenticationsOverTimeHistogram); export const AuthenticationsQueryTabBody = ({ deleteQuery, @@ -21,43 +26,69 @@ export const AuthenticationsQueryTabBody = ({ setQuery, startDate, type, + updateDateRange = () => {}, }: HostsComponentsQueryProps) => ( - - {({ - authentications, - totalCount, - loading, - pageInfo, - loadPage, - id, - inspect, - isInspected, - refetch, - }) => ( - - )} - + <> + + {({ authenticationsOverTime, loading, id, inspect, refetch, totalCount }) => ( + + )} + + + + {({ + authentications, + totalCount, + loading, + pageInfo, + loadPage, + id, + inspect, + isInspected, + refetch, + }) => ( + + )} + + ); AuthenticationsQueryTabBody.displayName = 'AuthenticationsQueryTabBody'; diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/events_query_tab_body.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/events_query_tab_body.tsx index 53bf73a1b9b7b..808565cadfa46 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/events_query_tab_body.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/events_query_tab_body.tsx @@ -21,7 +21,6 @@ export const EventsQueryTabBody = ({ filterQuery, setQuery, startDate, - timezone, updateDateRange = () => {}, }: HostsComponentsQueryProps) => { return ( @@ -31,7 +30,6 @@ export const EventsQueryTabBody = ({ filterQuery={filterQuery} sourceId="default" startDate={startDate} - timezone={timezone} type={hostsModel.HostsType.page} > {({ eventsOverTime, loading, id, inspect, refetch, totalCount }) => ( diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/types.ts b/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/types.ts index 552426602cdc5..d567038a05bd8 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/types.ts +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/types.ts @@ -29,7 +29,6 @@ interface QueryTabBodyProps { type: hostsModel.HostsType; startDate: number; endDate: number; - timezone?: string; filterQuery?: string | ESTermQuery; } diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/translations.ts b/x-pack/legacy/plugins/siem/public/pages/hosts/translations.ts index e7f6c3abbd3b5..1c95cbed71a4a 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/translations.ts @@ -17,7 +17,7 @@ export const PAGE_TITLE = i18n.translate('xpack.siem.hosts.pageTitle', { export const NAVIGATION_ALL_HOSTS_TITLE = i18n.translate( 'xpack.siem.hosts.navigation.allHostsTitle', { - defaultMessage: 'All Hosts', + defaultMessage: 'All hosts', } ); diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/types.ts b/x-pack/legacy/plugins/siem/public/pages/hosts/types.ts new file mode 100644 index 0000000000000..980c5535129aa --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/types.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 { StaticIndexPattern } from 'ui/index_patterns'; +import { ActionCreator } from 'typescript-fsa'; +import { Filter } from '@kbn/es-query'; +import { Query } from 'src/plugins/data/common'; + +import { SiemPageName } from '../home/types'; +import { hostsModel } from '../../store'; +import { InputsModelId } from '../../store/inputs/constants'; +import { GlobalTimeArgs } from '../../containers/global_time'; + +export const hostsPagePath = `/:pageName(${SiemPageName.hosts})`; +export const hostDetailsPagePath = `${hostsPagePath}/:detailName`; + +export interface HostsComponentReduxProps { + query: Query; + filters: Filter[]; +} + +export interface HostsComponentDispatchProps { + setAbsoluteRangeDatePicker: ActionCreator<{ + id: InputsModelId; + from: number; + to: number; + }>; + hostsPagePath: string; +} + +export type HostsTabsProps = HostsComponentDispatchProps & + HostsQueryProps & { + filterQuery: string; + type: hostsModel.HostsType; + indexPattern: StaticIndexPattern; + }; + +export type HostsQueryProps = GlobalTimeArgs; + +export type HostsComponentProps = HostsComponentReduxProps & + HostsComponentDispatchProps & + HostsQueryProps; diff --git a/x-pack/legacy/plugins/siem/public/pages/network/network.tsx b/x-pack/legacy/plugins/siem/public/pages/network/network.tsx index b10c09d65426b..f7b3cfb4962fc 100644 --- a/x-pack/legacy/plugins/siem/public/pages/network/network.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/network/network.tsx @@ -14,9 +14,9 @@ import { EmbeddedMap } from '../../components/embeddables/embedded_map'; import { FiltersGlobal } from '../../components/filters_global'; import { HeaderPage } from '../../components/header_page'; import { LastEventTime } from '../../components/last_event_time'; +import { SiemNavigation } from '../../components/navigation'; import { manageQuery } from '../../components/page/manage_query'; import { KpiNetworkComponent } from '../../components/page/network'; -import { SiemNavigation } from '../../components/navigation'; import { SiemSearchBar } from '../../components/search_bar'; import { KpiNetworkQuery } from '../../containers/kpi_network'; import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../containers/source'; @@ -26,7 +26,6 @@ import { convertToBuildEsQuery } from '../../lib/keury'; import { networkModel, State, inputsSelectors } from '../../store'; import { setAbsoluteRangeDatePicker as dispatchSetAbsoluteRangeDatePicker } from '../../store/inputs/actions'; import { SpyRoute } from '../../utils/route/spy_routes'; - import { navTabsNetwork, NetworkRoutes, NetworkRoutesLoading } from './navigation'; import { NetworkEmptyPage } from './network_empty_page'; import * as i18n from './translations'; @@ -78,6 +77,8 @@ const NetworkComponent = React.memo( setQuery={setQuery} /> + + state.app.notesById; const getErrors = (state: State): ErrorModel => state.app.errors; -const getNotes = (notesById: NotesById, noteIds: string[]) => +export const getNotes = memoizeOne((notesById: NotesById, noteIds: string[]): Note[] => keys(notesById).reduce((acc: Note[], noteId: string) => { if (noteIds.includes(noteId)) { const note: Note = notesById[noteId]; return [...acc, note]; } return acc; - }, []); + }, []) +); export const selectNotesByIdSelector = createSelector( selectNotesById, @@ -34,8 +35,7 @@ export const selectNotesByIdSelector = createSelector( export const notesByIdsSelector = () => createSelector( selectNotesById, - (notesById: NotesById) => - memoizeOne((noteIds: string[]): Note[] => getNotes(notesById, noteIds)) + (notesById: NotesById) => notesById ); export const errorsSelector = () => diff --git a/x-pack/legacy/plugins/siem/server/graphql/authentications/resolvers.ts b/x-pack/legacy/plugins/siem/server/graphql/authentications/resolvers.ts index b66ccd9a111b7..aaa66215e98f4 100644 --- a/x-pack/legacy/plugins/siem/server/graphql/authentications/resolvers.ts +++ b/x-pack/legacy/plugins/siem/server/graphql/authentications/resolvers.ts @@ -7,7 +7,7 @@ import { SourceResolvers } from '../../graphql/types'; import { Authentications } from '../../lib/authentications'; import { AppResolverOf, ChildResolverOf } from '../../lib/framework'; -import { createOptionsPaginated } from '../../utils/build_query/create_options'; +import { createOptionsPaginated, createOptions } from '../../utils/build_query/create_options'; import { QuerySourceResolver } from '../sources/resolvers'; type QueryAuthenticationsResolver = ChildResolverOf< @@ -15,6 +15,11 @@ type QueryAuthenticationsResolver = ChildResolverOf< QuerySourceResolver >; +type QueryAuthenticationsOverTimeResolver = ChildResolverOf< + AppResolverOf, + QuerySourceResolver +>; + export interface AuthenticationsResolversDeps { authentications: Authentications; } @@ -24,6 +29,7 @@ export const createAuthenticationsResolvers = ( ): { Source: { Authentications: QueryAuthenticationsResolver; + AuthenticationsOverTime: QueryAuthenticationsOverTimeResolver; }; } => ({ Source: { @@ -31,5 +37,12 @@ export const createAuthenticationsResolvers = ( const options = createOptionsPaginated(source, args, info); return libs.authentications.getAuthentications(req, options); }, + async AuthenticationsOverTime(source, args, { req }, info) { + const options = { + ...createOptions(source, args, info), + defaultIndex: args.defaultIndex, + }; + return libs.authentications.getAuthenticationsOverTime(req, options); + }, }, }); diff --git a/x-pack/legacy/plugins/siem/server/graphql/authentications/schema.gql.ts b/x-pack/legacy/plugins/siem/server/graphql/authentications/schema.gql.ts index 20935ce9ed03f..5a65ef5d678ce 100644 --- a/x-pack/legacy/plugins/siem/server/graphql/authentications/schema.gql.ts +++ b/x-pack/legacy/plugins/siem/server/graphql/authentications/schema.gql.ts @@ -34,6 +34,12 @@ export const authenticationsSchema = gql` inspect: Inspect } + type AuthenticationsOverTimeData { + inspect: Inspect + authenticationsOverTime: [MatrixOverTimeHistogramData!]! + totalCount: Float! + } + extend type Source { "Gets Authentication success and failures based on a timerange" Authentications( @@ -42,5 +48,10 @@ export const authenticationsSchema = gql` filterQuery: String defaultIndex: [String!]! ): AuthenticationsData! + AuthenticationsOverTime( + timerange: TimerangeInput! + filterQuery: String + defaultIndex: [String!]! + ): AuthenticationsOverTimeData! } `; diff --git a/x-pack/legacy/plugins/siem/server/graphql/types.ts b/x-pack/legacy/plugins/siem/server/graphql/types.ts index a87d321fc68d2..6de11e0652871 100644 --- a/x-pack/legacy/plugins/siem/server/graphql/types.ts +++ b/x-pack/legacy/plugins/siem/server/graphql/types.ts @@ -29,8 +29,6 @@ export interface TimerangeInput { to: number; /** The beginning of the timerange */ from: number; - /** The default browser set time zone */ - timezone?: Maybe; } export interface PaginationInputPaginated { @@ -405,6 +403,8 @@ export interface Source { /** Gets Authentication success and failures based on a timerange */ Authentications: AuthenticationsData; + AuthenticationsOverTime: AuthenticationsOverTimeData; + Timeline: TimelineData; TimelineDetails: TimelineDetailsData; @@ -638,6 +638,22 @@ export interface Inspect { response: string[]; } +export interface AuthenticationsOverTimeData { + inspect?: Maybe; + + authenticationsOverTime: MatrixOverTimeHistogramData[]; + + totalCount: number; +} + +export interface MatrixOverTimeHistogramData { + x: number; + + y: number; + + g: string; +} + export interface TimelineData { edges: TimelineEdges[]; @@ -1222,14 +1238,6 @@ export interface EventsOverTimeData { totalCount: number; } -export interface MatrixOverTimeHistogramData { - x: number; - - y: number; - - g: string; -} - export interface HostsData { edges: HostsEdges[]; @@ -1982,6 +1990,13 @@ export interface AuthenticationsSourceArgs { defaultIndex: string[]; } +export interface AuthenticationsOverTimeSourceArgs { + timerange: TimerangeInput; + + filterQuery?: Maybe; + + defaultIndex: string[]; +} export interface TimelineSourceArgs { pagination: PaginationInput; @@ -2588,6 +2603,12 @@ export namespace SourceResolvers { /** Gets Authentication success and failures based on a timerange */ Authentications?: AuthenticationsResolver; + AuthenticationsOverTime?: AuthenticationsOverTimeResolver< + AuthenticationsOverTimeData, + TypeParent, + TContext + >; + Timeline?: TimelineResolver; TimelineDetails?: TimelineDetailsResolver; @@ -2663,6 +2684,19 @@ export namespace SourceResolvers { defaultIndex: string[]; } + export type AuthenticationsOverTimeResolver< + R = AuthenticationsOverTimeData, + Parent = Source, + TContext = SiemContext + > = Resolver; + export interface AuthenticationsOverTimeArgs { + timerange: TimerangeInput; + + filterQuery?: Maybe; + + defaultIndex: string[]; + } + export type TimelineResolver< R = TimelineData, Parent = Source, @@ -3619,6 +3653,62 @@ export namespace InspectResolvers { >; } +export namespace AuthenticationsOverTimeDataResolvers { + export interface Resolvers { + inspect?: InspectResolver, TypeParent, TContext>; + + authenticationsOverTime?: AuthenticationsOverTimeResolver< + MatrixOverTimeHistogramData[], + TypeParent, + TContext + >; + + totalCount?: TotalCountResolver; + } + + export type InspectResolver< + R = Maybe, + Parent = AuthenticationsOverTimeData, + TContext = SiemContext + > = Resolver; + export type AuthenticationsOverTimeResolver< + R = MatrixOverTimeHistogramData[], + Parent = AuthenticationsOverTimeData, + TContext = SiemContext + > = Resolver; + export type TotalCountResolver< + R = number, + Parent = AuthenticationsOverTimeData, + TContext = SiemContext + > = Resolver; +} + +export namespace MatrixOverTimeHistogramDataResolvers { + export interface Resolvers { + x?: XResolver; + + y?: YResolver; + + g?: GResolver; + } + + export type XResolver< + R = number, + Parent = MatrixOverTimeHistogramData, + TContext = SiemContext + > = Resolver; + export type YResolver< + R = number, + Parent = MatrixOverTimeHistogramData, + TContext = SiemContext + > = Resolver; + export type GResolver< + R = string, + Parent = MatrixOverTimeHistogramData, + TContext = SiemContext + > = Resolver; +} + export namespace TimelineDataResolvers { export interface Resolvers { edges?: EdgesResolver; @@ -5569,32 +5659,6 @@ export namespace EventsOverTimeDataResolvers { > = Resolver; } -export namespace MatrixOverTimeHistogramDataResolvers { - export interface Resolvers { - x?: XResolver; - - y?: YResolver; - - g?: GResolver; - } - - export type XResolver< - R = number, - Parent = MatrixOverTimeHistogramData, - TContext = SiemContext - > = Resolver; - export type YResolver< - R = number, - Parent = MatrixOverTimeHistogramData, - TContext = SiemContext - > = Resolver; - export type GResolver< - R = string, - Parent = MatrixOverTimeHistogramData, - TContext = SiemContext - > = Resolver; -} - export namespace HostsDataResolvers { export interface Resolvers { edges?: EdgesResolver; @@ -8112,6 +8176,8 @@ export type IResolvers = { CursorType?: CursorTypeResolvers.Resolvers; PageInfoPaginated?: PageInfoPaginatedResolvers.Resolvers; Inspect?: InspectResolvers.Resolvers; + AuthenticationsOverTimeData?: AuthenticationsOverTimeDataResolvers.Resolvers; + MatrixOverTimeHistogramData?: MatrixOverTimeHistogramDataResolvers.Resolvers; TimelineData?: TimelineDataResolvers.Resolvers; TimelineEdges?: TimelineEdgesResolvers.Resolvers; TimelineItem?: TimelineItemResolvers.Resolvers; @@ -8163,7 +8229,6 @@ export type IResolvers = { DetailItem?: DetailItemResolvers.Resolvers; LastEventTimeData?: LastEventTimeDataResolvers.Resolvers; EventsOverTimeData?: EventsOverTimeDataResolvers.Resolvers; - MatrixOverTimeHistogramData?: MatrixOverTimeHistogramDataResolvers.Resolvers; HostsData?: HostsDataResolvers.Resolvers; HostsEdges?: HostsEdgesResolvers.Resolvers; HostItem?: HostItemResolvers.Resolvers; diff --git a/x-pack/legacy/plugins/siem/server/lib/authentications/elastic_adapter.test.ts b/x-pack/legacy/plugins/siem/server/lib/authentications/elasticsearch_adapter.test.ts similarity index 100% rename from x-pack/legacy/plugins/siem/server/lib/authentications/elastic_adapter.test.ts rename to x-pack/legacy/plugins/siem/server/lib/authentications/elasticsearch_adapter.test.ts diff --git a/x-pack/legacy/plugins/siem/server/lib/authentications/elasticsearch_adapter.ts b/x-pack/legacy/plugins/siem/server/lib/authentications/elasticsearch_adapter.ts index 79f13ce4461e5..9a9e30bf01c04 100644 --- a/x-pack/legacy/plugins/siem/server/lib/authentications/elasticsearch_adapter.ts +++ b/x-pack/legacy/plugins/siem/server/lib/authentications/elasticsearch_adapter.ts @@ -6,20 +6,50 @@ import { getOr } from 'lodash/fp'; -import { AuthenticationsData, AuthenticationsEdges } from '../../graphql/types'; +import { + AuthenticationsData, + AuthenticationsEdges, + AuthenticationsOverTimeData, + MatrixOverTimeHistogramData, +} from '../../graphql/types'; import { mergeFieldsWithHit, inspectStringifyObject } from '../../utils/build_query'; -import { FrameworkAdapter, FrameworkRequest, RequestOptionsPaginated } from '../framework'; +import { + FrameworkAdapter, + FrameworkRequest, + RequestOptionsPaginated, + RequestBasicOptions, +} from '../framework'; import { TermAggregation } from '../types'; import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../common/constants'; import { auditdFieldsMap, buildQuery } from './query.dsl'; +import { buildAuthenticationsOverTimeQuery } from './query.authentications_over_time.dsl'; import { AuthenticationBucket, AuthenticationData, AuthenticationHit, AuthenticationsAdapter, + AuthenticationsActionGroupData, } from './types'; +const getAuthenticationsOverTimeByAuthenticationResult = ( + data: AuthenticationsActionGroupData[] +): MatrixOverTimeHistogramData[] => { + let result: MatrixOverTimeHistogramData[] = []; + data.forEach(({ key: group, events }) => { + const eventsData = getOr([], 'buckets', events).map( + ({ key, doc_count }: { key: number; doc_count: number }) => ({ + x: key, + y: doc_count, + g: group, + }) + ); + result = [...result, ...eventsData]; + }); + + return result; +}; + export class ElasticsearchAuthenticationAdapter implements AuthenticationsAdapter { constructor(private readonly framework: FrameworkAdapter) {} @@ -79,6 +109,35 @@ export class ElasticsearchAuthenticationAdapter implements AuthenticationsAdapte }, }; } + + public async getAuthenticationsOverTime( + request: FrameworkRequest, + options: RequestBasicOptions + ): Promise { + const dsl = buildAuthenticationsOverTimeQuery(options); + const response = await this.framework.callWithRequest( + request, + 'search', + dsl + ); + const totalCount = getOr(0, 'hits.total.value', response); + const authenticationsOverTimeBucket = getOr( + [], + 'aggregations.eventActionGroup.buckets', + response + ); + const inspect = { + dsl: [inspectStringifyObject(dsl)], + response: [inspectStringifyObject(response)], + }; + return { + inspect, + authenticationsOverTime: getAuthenticationsOverTimeByAuthenticationResult( + authenticationsOverTimeBucket + ), + totalCount, + }; + } } export const formatAuthenticationData = ( diff --git a/x-pack/legacy/plugins/siem/server/lib/authentications/index.ts b/x-pack/legacy/plugins/siem/server/lib/authentications/index.ts index c1b93818943db..b369c358e1619 100644 --- a/x-pack/legacy/plugins/siem/server/lib/authentications/index.ts +++ b/x-pack/legacy/plugins/siem/server/lib/authentications/index.ts @@ -5,9 +5,10 @@ */ import { AuthenticationsData } from '../../graphql/types'; -import { FrameworkRequest, RequestOptionsPaginated } from '../framework'; +import { FrameworkRequest, RequestOptionsPaginated, RequestBasicOptions } from '../framework'; import { AuthenticationsAdapter } from './types'; +import { AuthenticationsOverTimeData } from '../../../public/graphql/types'; export class Authentications { constructor(private readonly adapter: AuthenticationsAdapter) {} @@ -18,4 +19,11 @@ export class Authentications { ): Promise { return this.adapter.getAuthentications(req, options); } + + public async getAuthenticationsOverTime( + req: FrameworkRequest, + options: RequestBasicOptions + ): Promise { + return this.adapter.getAuthenticationsOverTime(req, options); + } } diff --git a/x-pack/legacy/plugins/siem/server/lib/authentications/query.authentications_over_time.dsl.ts b/x-pack/legacy/plugins/siem/server/lib/authentications/query.authentications_over_time.dsl.ts new file mode 100644 index 0000000000000..e2ff0013e063c --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/authentications/query.authentications_over_time.dsl.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 { createQueryFilterClauses, calculateTimeseriesInterval } from '../../utils/build_query'; +import { RequestBasicOptions } from '../framework'; + +export const buildAuthenticationsOverTimeQuery = ({ + filterQuery, + timerange: { from, to }, + defaultIndex, + sourceConfiguration: { + fields: { timestamp }, + }, +}: RequestBasicOptions) => { + const filter = [ + ...createQueryFilterClauses(filterQuery), + { + range: { + [timestamp]: { + gte: from, + lte: to, + }, + }, + }, + ]; + + const getHistogramAggregation = () => { + const minIntervalSeconds = 10; + const interval = calculateTimeseriesInterval(from, to, minIntervalSeconds); + const histogramTimestampField = '@timestamp'; + const dateHistogram = { + date_histogram: { + field: histogramTimestampField, + fixed_interval: `${interval}s`, + }, + }; + const autoDateHistogram = { + auto_date_histogram: { + field: histogramTimestampField, + buckets: 36, + }, + }; + return { + eventActionGroup: { + terms: { + field: 'event.type', + include: ['authentication_success', 'authentication_failure'], + order: { + _count: 'desc', + }, + size: 2, + }, + aggs: { + events: interval ? dateHistogram : autoDateHistogram, + }, + }, + }; + }; + + const dslQuery = { + index: defaultIndex, + allowNoIndices: true, + ignoreUnavailable: true, + body: { + aggregations: getHistogramAggregation(), + query: { + bool: { + filter, + }, + }, + size: 0, + track_total_hits: true, + }, + }; + + return dslQuery; +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/authentications/types.ts b/x-pack/legacy/plugins/siem/server/lib/authentications/types.ts index 2d2c7ba547c09..6e83a2bdba956 100644 --- a/x-pack/legacy/plugins/siem/server/lib/authentications/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/authentications/types.ts @@ -4,8 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AuthenticationsData, LastSourceHost } from '../../graphql/types'; -import { FrameworkRequest, RequestOptionsPaginated } from '../framework'; +import { + AuthenticationsData, + AuthenticationsOverTimeData, + LastSourceHost, +} from '../../graphql/types'; +import { FrameworkRequest, RequestOptionsPaginated, RequestBasicOptions } from '../framework'; import { Hit, SearchHit, TotalHit } from '../types'; export interface AuthenticationsAdapter { @@ -13,6 +17,10 @@ export interface AuthenticationsAdapter { req: FrameworkRequest, options: RequestOptionsPaginated ): Promise; + getAuthenticationsOverTime( + req: FrameworkRequest, + options: RequestBasicOptions + ): Promise; } type StringOrNumber = string | number; @@ -60,3 +68,17 @@ export interface AuthenticationData extends SearchHit { }; }; } + +interface AuthenticationsOverTimeHistogramData { + key_as_string: string; + key: number; + doc_count: number; +} + +export interface AuthenticationsActionGroupData { + key: number; + events: { + bucket: AuthenticationsOverTimeHistogramData[]; + }; + doc_count: number; +} diff --git a/x-pack/legacy/plugins/siem/server/lib/events/query.events_over_time.dsl.ts b/x-pack/legacy/plugins/siem/server/lib/events/query.events_over_time.dsl.ts index 357c1c24119bb..e655485638e16 100644 --- a/x-pack/legacy/plugins/siem/server/lib/events/query.events_over_time.dsl.ts +++ b/x-pack/legacy/plugins/siem/server/lib/events/query.events_over_time.dsl.ts @@ -8,7 +8,7 @@ import { RequestBasicOptions } from '../framework'; export const buildEventsOverTimeQuery = ({ filterQuery, - timerange: { from, timezone, to }, + timerange: { from, to }, defaultIndex, sourceConfiguration: { fields: { timestamp }, @@ -21,7 +21,6 @@ export const buildEventsOverTimeQuery = ({ [timestamp]: { gte: from, lte: to, - ...(timezone && { time_zone: timezone }), }, }, }, diff --git a/x-pack/legacy/plugins/siem/server/lib/hosts/elasticsearch_adapter.test.ts b/x-pack/legacy/plugins/siem/server/lib/hosts/elasticsearch_adapter.test.ts index 979f3a832919c..fc5c037dfbb5f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/hosts/elasticsearch_adapter.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/hosts/elasticsearch_adapter.test.ts @@ -49,41 +49,33 @@ describe('hosts elasticsearch_adapter', () => { describe('#formatHostsData', () => { const buckets: HostAggEsItem = { key: 'zeek-london', - host_os_version: { - buckets: [ - { - key: '18.04.2 LTS (Bionic Beaver)', - doc_count: 1467783, - timestamp: { value: 1554516350177, value_as_string: '2019-04-06T02:05:50.177Z' }, + os: { + hits: { + total: { + value: 242338, + relation: 'eq', }, - ], - }, - host_os_name: { - buckets: [ - { - key: 'Ubuntu', - doc_count: 1467783, - timestamp: { value: 1554516350177, value_as_string: '2019-04-06T02:05:50.177Z' }, - }, - ], - }, - host_name: { - buckets: [ - { - key: 'zeek-london', - doc_count: 1467783, - timestamp: { value: 1554516350177, value_as_string: '2019-04-06T02:05:50.177Z' }, - }, - ], - }, - host_id: { - buckets: [ - { - key: '7c21f5ed03b04d0299569d221fe18bbc', - doc_count: 1467783, - timestamp: { value: 1554516350177, value_as_string: '2019-04-06T02:05:50.177Z' }, - }, - ], + max_score: null, + hits: [ + { + _index: 'auditbeat-8.0.0-2019.09.06-000022', + _id: 'dl0T_m0BHe9nqdOiF2A8', + _score: null, + _source: { + host: { + os: { + kernel: '5.0.0-1013-gcp', + name: 'Ubuntu', + family: 'debian', + version: '18.04.2 LTS (Bionic Beaver)', + platform: 'ubuntu', + }, + }, + }, + sort: [1571925726017], + }, + ], + }, }, }; diff --git a/x-pack/legacy/plugins/siem/server/lib/hosts/elasticsearch_adapter.ts b/x-pack/legacy/plugins/siem/server/lib/hosts/elasticsearch_adapter.ts index 792a1a4be5307..1b69863a443bc 100644 --- a/x-pack/legacy/plugins/siem/server/lib/hosts/elasticsearch_adapter.ts +++ b/x-pack/legacy/plugins/siem/server/lib/hosts/elasticsearch_adapter.ts @@ -117,7 +117,6 @@ export const formatHostEdgesData = (fields: readonly string[], bucket: HostAggEs const hostId = get('key', bucket); flattenedFields.node._id = hostId || null; flattenedFields.cursor.value = hostId || ''; - const fieldValue = getHostFieldValue(fieldName, bucket); if (fieldValue != null) { return set(`node.${fieldName}`, fieldValue, flattenedFields); @@ -164,6 +163,15 @@ const getHostFieldValue = (fieldName: string, bucket: HostAggEsItem): string | s } else if (has(aggField, bucket)) { const valueObj: HostValue = get(aggField, bucket); return valueObj.value_as_string; + } else if (['host.name', 'host.os.name', 'host.os.version'].includes(fieldName)) { + switch (fieldName) { + case 'host.name': + return get('key', bucket) || null; + case 'host.os.name': + return get('os.hits.hits[0]._source.host.os.name', bucket) || null; + case 'host.os.version': + return get('os.hits.hits[0]._source.host.os.version', bucket) || null; + } } return null; }; diff --git a/x-pack/legacy/plugins/siem/server/lib/hosts/helpers.ts b/x-pack/legacy/plugins/siem/server/lib/hosts/helpers.ts index fbc7a2146a9b5..886c2d521caba 100644 --- a/x-pack/legacy/plugins/siem/server/lib/hosts/helpers.ts +++ b/x-pack/legacy/plugins/siem/server/lib/hosts/helpers.ts @@ -10,12 +10,12 @@ export const buildFieldsTermAggregation = (esFields: readonly string[]): Aggrega esFields.reduce( (res, field) => ({ ...res, - ...getAggregationTypeFromField(field), + ...getTermsAggregationTypeFromField(field), }), {} ); -const getAggregationTypeFromField = (field: string): AggregationRequest => { +const getTermsAggregationTypeFromField = (field: string): AggregationRequest => { return { [field.replace(/\./g, '_')]: { terms: { diff --git a/x-pack/legacy/plugins/siem/server/lib/hosts/query.hosts.dsl.ts b/x-pack/legacy/plugins/siem/server/lib/hosts/query.hosts.dsl.ts index 0e1ed8f6011c2..70f57769362f5 100644 --- a/x-pack/legacy/plugins/siem/server/lib/hosts/query.hosts.dsl.ts +++ b/x-pack/legacy/plugins/siem/server/lib/hosts/query.hosts.dsl.ts @@ -6,11 +6,8 @@ import { Direction, HostsFields, HostsSortField } from '../../graphql/types'; import { assertUnreachable, createQueryFilterClauses } from '../../utils/build_query'; -import { reduceFields } from '../../utils/build_query/reduce_fields'; -import { hostFieldsMap } from '../ecs_fields'; import { HostsRequestOptions } from '.'; -import { buildFieldsTermAggregation } from './helpers'; export const buildHostsQuery = ({ defaultIndex, @@ -23,8 +20,6 @@ export const buildHostsQuery = ({ }, timerange: { from, to }, }: HostsRequestOptions) => { - const esFields = reduceFields(fields, hostFieldsMap); - const filter = [ ...createQueryFilterClauses(filterQuery), { @@ -50,9 +45,21 @@ export const buildHostsQuery = ({ terms: { size: querySize, field: 'host.name', order: getQueryOrder(sort) }, aggs: { lastSeen: { max: { field: '@timestamp' } }, - ...buildFieldsTermAggregation( - esFields.filter(field => !['@timestamp', '_id'].includes(field)) - ), + os: { + top_hits: { + size: 1, + sort: [ + { + '@timestamp': { + order: 'desc', + }, + }, + ], + _source: { + includes: ['host.os.*'], + }, + }, + }, }, }, }, diff --git a/x-pack/legacy/plugins/siem/server/lib/hosts/types.ts b/x-pack/legacy/plugins/siem/server/lib/hosts/types.ts index 63360492c1cf3..e52cfe9d7feeb 100644 --- a/x-pack/legacy/plugins/siem/server/lib/hosts/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/hosts/types.ts @@ -10,11 +10,13 @@ import { HostItem, HostsData, HostsSortField, + Maybe, + OsEcsFields, SourceConfiguration, TimerangeInput, } from '../../graphql/types'; import { FrameworkRequest, RequestOptionsPaginated } from '../framework'; -import { Hit, Hits, SearchHit } from '../types'; +import { Hit, Hits, SearchHit, TotalValue } from '../types'; export interface HostsAdapter { getHosts(req: FrameworkRequest, options: HostsRequestOptions): Promise; @@ -71,23 +73,38 @@ export interface HostBuckets { buckets: HostBucketItem[]; } +export interface HostOsHitsItem { + hits: { + total: TotalValue | number; + max_score: number | null; + hits: Array<{ + _source: { host: { os: Maybe } }; + sort?: [number]; + _index?: string; + _type?: string; + _id?: string; + _score?: number | null; + }>; + }; +} + export interface HostAggEsItem { cloud_instance_id?: HostBuckets; cloud_machine_type?: HostBuckets; cloud_provider?: HostBuckets; cloud_region?: HostBuckets; - key?: string; firstSeen?: HostValue; - lastSeen?: HostValue; host_architecture?: HostBuckets; host_id?: HostBuckets; host_ip?: HostBuckets; host_mac?: HostBuckets; host_name?: HostBuckets; - host_os?: HostBuckets; host_os_name?: HostBuckets; host_os_version?: HostBuckets; host_type?: HostBuckets; + key?: string; + lastSeen?: HostValue; + os?: HostOsHitsItem; } export interface HostEsData extends SearchHit { diff --git a/x-pack/legacy/plugins/siem/server/lib/network/__snapshots__/elastic_adapter.test.ts.snap b/x-pack/legacy/plugins/siem/server/lib/network/__snapshots__/elastic_adapter.test.ts.snap new file mode 100644 index 0000000000000..50454fcb6b351 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/network/__snapshots__/elastic_adapter.test.ts.snap @@ -0,0 +1,1366 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Network Top N flow elasticsearch_adapter with FlowTarget=source Unhappy Path - No geo data getNetworkTopNFlow 1`] = ` +Object { + "edges": Array [ + Object { + "cursor": Object { + "tiebreaker": null, + "value": "1.1.1.1", + }, + "node": Object { + "_id": "1.1.1.1", + "network": Object { + "bytes_in": 11276023407, + "bytes_out": 1025631, + }, + "source": Object { + "autonomous_system": Object { + "name": "Level 3 Parent, LLC", + "number": 3356, + }, + "destination_ips": 345345, + "domain": Array [ + "test.1.net", + ], + "flows": 1234567, + "ip": "1.1.1.1", + "location": null, + }, + }, + }, + Object { + "cursor": Object { + "tiebreaker": null, + "value": "2.2.2.2", + }, + "node": Object { + "_id": "2.2.2.2", + "network": Object { + "bytes_in": 5469323342, + "bytes_out": 2811441, + }, + "source": Object { + "autonomous_system": Object { + "name": "Level 3 Parent, LLC", + "number": 3356, + }, + "destination_ips": 345345, + "domain": Array [ + "test.2.net", + ], + "flows": 1234567, + "ip": "2.2.2.2", + "location": Object { + "flowTarget": "source", + "geo": Object { + "city_name": "Philadelphia", + "continent_name": "North America", + "country_iso_code": "US", + "location": Object { + "lat": 39.9359, + "lon": -75.1534, + }, + "region_iso_code": "US-PA", + "region_name": "Pennsylvania", + }, + }, + }, + }, + }, + Object { + "cursor": Object { + "tiebreaker": null, + "value": "3.3.3.3", + }, + "node": Object { + "_id": "3.3.3.3", + "network": Object { + "bytes_in": 3807671322, + "bytes_out": 4494034, + }, + "source": Object { + "autonomous_system": Object { + "name": "Level 3 Parent, LLC", + "number": 3356, + }, + "destination_ips": 345345, + "domain": Array [ + "test.3.com", + "test.3-duplicate.com", + ], + "flows": 1234567, + "ip": "3.3.3.3", + "location": Object { + "flowTarget": "source", + "geo": Object { + "city_name": "Philadelphia", + "continent_name": "North America", + "country_iso_code": "US", + "location": Object { + "lat": 39.9359, + "lon": -75.1534, + }, + "region_iso_code": "US-PA", + "region_name": "Pennsylvania", + }, + }, + }, + }, + }, + Object { + "cursor": Object { + "tiebreaker": null, + "value": "4.4.4.4", + }, + "node": Object { + "_id": "4.4.4.4", + "network": Object { + "bytes_in": 166517626, + "bytes_out": 3194782, + }, + "source": Object { + "autonomous_system": Object { + "name": "Level 3 Parent, LLC", + "number": 3356, + }, + "destination_ips": 345345, + "domain": Array [ + "test.4.com", + ], + "flows": 1234567, + "ip": "4.4.4.4", + "location": Object { + "flowTarget": "source", + "geo": Object { + "city_name": "Philadelphia", + "continent_name": "North America", + "country_iso_code": "US", + "location": Object { + "lat": 39.9359, + "lon": -75.1534, + }, + "region_iso_code": "US-PA", + "region_name": "Pennsylvania", + }, + }, + }, + }, + }, + Object { + "cursor": Object { + "tiebreaker": null, + "value": "5.5.5.5", + }, + "node": Object { + "_id": "5.5.5.5", + "network": Object { + "bytes_in": 104785026, + "bytes_out": 1838597, + }, + "source": Object { + "autonomous_system": Object { + "name": "Level 3 Parent, LLC", + "number": 3356, + }, + "destination_ips": 345345, + "domain": Array [ + "test.5.com", + ], + "flows": 1234567, + "ip": "5.5.5.5", + "location": Object { + "flowTarget": "source", + "geo": Object { + "city_name": "Philadelphia", + "continent_name": "North America", + "country_iso_code": "US", + "location": Object { + "lat": 39.9359, + "lon": -75.1534, + }, + "region_iso_code": "US-PA", + "region_name": "Pennsylvania", + }, + }, + }, + }, + }, + Object { + "cursor": Object { + "tiebreaker": null, + "value": "6.6.6.6", + }, + "node": Object { + "_id": "6.6.6.6", + "network": Object { + "bytes_in": 28804250, + "bytes_out": 482982, + }, + "source": Object { + "autonomous_system": Object { + "name": "Level 3 Parent, LLC", + "number": 3356, + }, + "destination_ips": 345345, + "domain": Array [ + "test.6.com", + ], + "flows": 1234567, + "ip": "6.6.6.6", + "location": Object { + "flowTarget": "source", + "geo": Object { + "city_name": "Philadelphia", + "continent_name": "North America", + "country_iso_code": "US", + "location": Object { + "lat": 39.9359, + "lon": -75.1534, + }, + "region_iso_code": "US-PA", + "region_name": "Pennsylvania", + }, + }, + }, + }, + }, + Object { + "cursor": Object { + "tiebreaker": null, + "value": "7.7.7.7", + }, + "node": Object { + "_id": "7.7.7.7", + "network": Object { + "bytes_in": 23032363, + "bytes_out": 400623, + }, + "source": Object { + "autonomous_system": Object { + "name": "Level 3 Parent, LLC", + "number": 3356, + }, + "destination_ips": 345345, + "domain": Array [ + "test.7.com", + ], + "flows": 1234567, + "ip": "7.7.7.7", + "location": Object { + "flowTarget": "source", + "geo": Object { + "city_name": "Philadelphia", + "continent_name": "North America", + "country_iso_code": "US", + "location": Object { + "lat": 39.9359, + "lon": -75.1534, + }, + "region_iso_code": "US-PA", + "region_name": "Pennsylvania", + }, + }, + }, + }, + }, + Object { + "cursor": Object { + "tiebreaker": null, + "value": "8.8.8.8", + }, + "node": Object { + "_id": "8.8.8.8", + "network": Object { + "bytes_in": 21424889, + "bytes_out": 344357, + }, + "source": Object { + "autonomous_system": Object { + "name": "Level 3 Parent, LLC", + "number": 3356, + }, + "destination_ips": 345345, + "domain": Array [ + "test.8.com", + ], + "flows": 1234567, + "ip": "8.8.8.8", + "location": Object { + "flowTarget": "source", + "geo": Object { + "city_name": "Philadelphia", + "continent_name": "North America", + "country_iso_code": "US", + "location": Object { + "lat": 39.9359, + "lon": -75.1534, + }, + "region_iso_code": "US-PA", + "region_name": "Pennsylvania", + }, + }, + }, + }, + }, + Object { + "cursor": Object { + "tiebreaker": null, + "value": "9.9.9.9", + }, + "node": Object { + "_id": "9.9.9.9", + "network": Object { + "bytes_in": 19205000, + "bytes_out": 355663, + }, + "source": Object { + "autonomous_system": Object { + "name": "Level 3 Parent, LLC", + "number": 3356, + }, + "destination_ips": 345345, + "domain": Array [ + "test.9.com", + ], + "flows": 1234567, + "ip": "9.9.9.9", + "location": Object { + "flowTarget": "source", + "geo": Object { + "city_name": "Philadelphia", + "continent_name": "North America", + "country_iso_code": "US", + "location": Object { + "lat": 39.9359, + "lon": -75.1534, + }, + "region_iso_code": "US-PA", + "region_name": "Pennsylvania", + }, + }, + }, + }, + }, + Object { + "cursor": Object { + "tiebreaker": null, + "value": "10.10.10.10", + }, + "node": Object { + "_id": "10.10.10.10", + "network": Object { + "bytes_in": 11407633, + "bytes_out": 199360, + }, + "source": Object { + "autonomous_system": Object { + "name": "Level 3 Parent, LLC", + "number": 3356, + }, + "destination_ips": 345345, + "domain": Array [ + "test.10.com", + ], + "flows": 1234567, + "ip": "10.10.10.10", + "location": Object { + "flowTarget": "source", + "geo": Object { + "city_name": "Philadelphia", + "continent_name": "North America", + "country_iso_code": "US", + "location": Object { + "lat": 39.9359, + "lon": -75.1534, + }, + "region_iso_code": "US-PA", + "region_name": "Pennsylvania", + }, + }, + }, + }, + }, + ], + "inspect": Object { + "dsl": Array [ + "{ + \\"mockTopNFlowQueryDsl\\": \\"mockTopNFlowQueryDsl\\" +}", + ], + "response": Array [ + "{ + \\"took\\": 122, + \\"timed_out\\": false, + \\"_shards\\": { + \\"total\\": 11, + \\"successful\\": 11, + \\"skipped\\": 0, + \\"failed\\": 0 + }, + \\"hits\\": { + \\"max_score\\": null, + \\"hits\\": [] + }, + \\"aggregations\\": { + \\"top_n_flow_count\\": { + \\"value\\": 545 + }, + \\"source\\": { + \\"buckets\\": [ + { + \\"key\\": \\"1.1.1.1\\", + \\"flows\\": { + \\"value\\": 1234567 + }, + \\"destination_ips\\": { + \\"value\\": 345345 + }, + \\"bytes_in\\": { + \\"value\\": 11276023407 + }, + \\"bytes_out\\": { + \\"value\\": 1025631 + }, + \\"location\\": { + \\"doc_count\\": 14, + \\"top_geo\\": { + \\"hits\\": { + \\"total\\": { + \\"value\\": 14, + \\"relation\\": \\"eq\\" + }, + \\"max_score\\": 1, + \\"hits\\": [] + } + } + }, + \\"autonomous_system\\": { + \\"doc_count\\": 14, + \\"top_as\\": { + \\"hits\\": { + \\"total\\": { + \\"value\\": 14, + \\"relation\\": \\"eq\\" + }, + \\"max_score\\": 1, + \\"hits\\": [ + { + \\"_index\\": \\"filebeat-8.0.0-2019.06.19-000005\\", + \\"_type\\": \\"_doc\\", + \\"_id\\": \\"dd4fa2d4bd-692279846149410\\", + \\"_score\\": 1, + \\"_source\\": { + \\"source\\": { + \\"as\\": { + \\"number\\": 3356, + \\"organization\\": { + \\"name\\": \\"Level 3 Parent, LLC\\" + } + } + } + } + } + ] + } + } + }, + \\"domain\\": { + \\"buckets\\": [ + { + \\"key\\": \\"test.1.net\\" + } + ] + } + }, + { + \\"key\\": \\"2.2.2.2\\", + \\"flows\\": { + \\"value\\": 1234567 + }, + \\"destination_ips\\": { + \\"value\\": 345345 + }, + \\"bytes_in\\": { + \\"value\\": 5469323342 + }, + \\"bytes_out\\": { + \\"value\\": 2811441 + }, + \\"location\\": { + \\"doc_count\\": 14, + \\"top_geo\\": { + \\"hits\\": { + \\"total\\": { + \\"value\\": 14, + \\"relation\\": \\"eq\\" + }, + \\"max_score\\": 1, + \\"hits\\": [ + { + \\"_index\\": \\"filebeat-8.0.0-2019.06.19-000005\\", + \\"_type\\": \\"_doc\\", + \\"_id\\": \\"dd4fa2d4bd-692279846149410\\", + \\"_score\\": 1, + \\"_source\\": { + \\"source\\": { + \\"geo\\": { + \\"continent_name\\": \\"North America\\", + \\"region_iso_code\\": \\"US-PA\\", + \\"city_name\\": \\"Philadelphia\\", + \\"country_iso_code\\": \\"US\\", + \\"region_name\\": \\"Pennsylvania\\", + \\"location\\": { + \\"lon\\": -75.1534, + \\"lat\\": 39.9359 + } + } + } + } + } + ] + } + } + }, + \\"autonomous_system\\": { + \\"doc_count\\": 14, + \\"top_as\\": { + \\"hits\\": { + \\"total\\": { + \\"value\\": 14, + \\"relation\\": \\"eq\\" + }, + \\"max_score\\": 1, + \\"hits\\": [ + { + \\"_index\\": \\"filebeat-8.0.0-2019.06.19-000005\\", + \\"_type\\": \\"_doc\\", + \\"_id\\": \\"dd4fa2d4bd-692279846149410\\", + \\"_score\\": 1, + \\"_source\\": { + \\"source\\": { + \\"as\\": { + \\"number\\": 3356, + \\"organization\\": { + \\"name\\": \\"Level 3 Parent, LLC\\" + } + } + } + } + } + ] + } + } + }, + \\"domain\\": { + \\"buckets\\": [ + { + \\"key\\": \\"test.2.net\\" + } + ] + } + }, + { + \\"key\\": \\"3.3.3.3\\", + \\"flows\\": { + \\"value\\": 1234567 + }, + \\"destination_ips\\": { + \\"value\\": 345345 + }, + \\"bytes_in\\": { + \\"value\\": 3807671322 + }, + \\"bytes_out\\": { + \\"value\\": 4494034 + }, + \\"location\\": { + \\"doc_count\\": 14, + \\"top_geo\\": { + \\"hits\\": { + \\"total\\": { + \\"value\\": 14, + \\"relation\\": \\"eq\\" + }, + \\"max_score\\": 1, + \\"hits\\": [ + { + \\"_index\\": \\"filebeat-8.0.0-2019.06.19-000005\\", + \\"_type\\": \\"_doc\\", + \\"_id\\": \\"dd4fa2d4bd-692279846149410\\", + \\"_score\\": 1, + \\"_source\\": { + \\"source\\": { + \\"geo\\": { + \\"continent_name\\": \\"North America\\", + \\"region_iso_code\\": \\"US-PA\\", + \\"city_name\\": \\"Philadelphia\\", + \\"country_iso_code\\": \\"US\\", + \\"region_name\\": \\"Pennsylvania\\", + \\"location\\": { + \\"lon\\": -75.1534, + \\"lat\\": 39.9359 + } + } + } + } + } + ] + } + } + }, + \\"autonomous_system\\": { + \\"doc_count\\": 14, + \\"top_as\\": { + \\"hits\\": { + \\"total\\": { + \\"value\\": 14, + \\"relation\\": \\"eq\\" + }, + \\"max_score\\": 1, + \\"hits\\": [ + { + \\"_index\\": \\"filebeat-8.0.0-2019.06.19-000005\\", + \\"_type\\": \\"_doc\\", + \\"_id\\": \\"dd4fa2d4bd-692279846149410\\", + \\"_score\\": 1, + \\"_source\\": { + \\"source\\": { + \\"as\\": { + \\"number\\": 3356, + \\"organization\\": { + \\"name\\": \\"Level 3 Parent, LLC\\" + } + } + } + } + } + ] + } + } + }, + \\"domain\\": { + \\"buckets\\": [ + { + \\"key\\": \\"test.3.com\\" + }, + { + \\"key\\": \\"test.3-duplicate.com\\" + } + ] + } + }, + { + \\"key\\": \\"4.4.4.4\\", + \\"flows\\": { + \\"value\\": 1234567 + }, + \\"destination_ips\\": { + \\"value\\": 345345 + }, + \\"bytes_in\\": { + \\"value\\": 166517626 + }, + \\"bytes_out\\": { + \\"value\\": 3194782 + }, + \\"location\\": { + \\"doc_count\\": 14, + \\"top_geo\\": { + \\"hits\\": { + \\"total\\": { + \\"value\\": 14, + \\"relation\\": \\"eq\\" + }, + \\"max_score\\": 1, + \\"hits\\": [ + { + \\"_index\\": \\"filebeat-8.0.0-2019.06.19-000005\\", + \\"_type\\": \\"_doc\\", + \\"_id\\": \\"dd4fa2d4bd-692279846149410\\", + \\"_score\\": 1, + \\"_source\\": { + \\"source\\": { + \\"geo\\": { + \\"continent_name\\": \\"North America\\", + \\"region_iso_code\\": \\"US-PA\\", + \\"city_name\\": \\"Philadelphia\\", + \\"country_iso_code\\": \\"US\\", + \\"region_name\\": \\"Pennsylvania\\", + \\"location\\": { + \\"lon\\": -75.1534, + \\"lat\\": 39.9359 + } + } + } + } + } + ] + } + } + }, + \\"autonomous_system\\": { + \\"doc_count\\": 14, + \\"top_as\\": { + \\"hits\\": { + \\"total\\": { + \\"value\\": 14, + \\"relation\\": \\"eq\\" + }, + \\"max_score\\": 1, + \\"hits\\": [ + { + \\"_index\\": \\"filebeat-8.0.0-2019.06.19-000005\\", + \\"_type\\": \\"_doc\\", + \\"_id\\": \\"dd4fa2d4bd-692279846149410\\", + \\"_score\\": 1, + \\"_source\\": { + \\"source\\": { + \\"as\\": { + \\"number\\": 3356, + \\"organization\\": { + \\"name\\": \\"Level 3 Parent, LLC\\" + } + } + } + } + } + ] + } + } + }, + \\"domain\\": { + \\"buckets\\": [ + { + \\"key\\": \\"test.4.com\\" + } + ] + } + }, + { + \\"key\\": \\"5.5.5.5\\", + \\"flows\\": { + \\"value\\": 1234567 + }, + \\"destination_ips\\": { + \\"value\\": 345345 + }, + \\"bytes_in\\": { + \\"value\\": 104785026 + }, + \\"bytes_out\\": { + \\"value\\": 1838597 + }, + \\"location\\": { + \\"doc_count\\": 14, + \\"top_geo\\": { + \\"hits\\": { + \\"total\\": { + \\"value\\": 14, + \\"relation\\": \\"eq\\" + }, + \\"max_score\\": 1, + \\"hits\\": [ + { + \\"_index\\": \\"filebeat-8.0.0-2019.06.19-000005\\", + \\"_type\\": \\"_doc\\", + \\"_id\\": \\"dd4fa2d4bd-692279846149410\\", + \\"_score\\": 1, + \\"_source\\": { + \\"source\\": { + \\"geo\\": { + \\"continent_name\\": \\"North America\\", + \\"region_iso_code\\": \\"US-PA\\", + \\"city_name\\": \\"Philadelphia\\", + \\"country_iso_code\\": \\"US\\", + \\"region_name\\": \\"Pennsylvania\\", + \\"location\\": { + \\"lon\\": -75.1534, + \\"lat\\": 39.9359 + } + } + } + } + } + ] + } + } + }, + \\"autonomous_system\\": { + \\"doc_count\\": 14, + \\"top_as\\": { + \\"hits\\": { + \\"total\\": { + \\"value\\": 14, + \\"relation\\": \\"eq\\" + }, + \\"max_score\\": 1, + \\"hits\\": [ + { + \\"_index\\": \\"filebeat-8.0.0-2019.06.19-000005\\", + \\"_type\\": \\"_doc\\", + \\"_id\\": \\"dd4fa2d4bd-692279846149410\\", + \\"_score\\": 1, + \\"_source\\": { + \\"source\\": { + \\"as\\": { + \\"number\\": 3356, + \\"organization\\": { + \\"name\\": \\"Level 3 Parent, LLC\\" + } + } + } + } + } + ] + } + } + }, + \\"domain\\": { + \\"buckets\\": [ + { + \\"key\\": \\"test.5.com\\" + } + ] + } + }, + { + \\"key\\": \\"6.6.6.6\\", + \\"flows\\": { + \\"value\\": 1234567 + }, + \\"destination_ips\\": { + \\"value\\": 345345 + }, + \\"bytes_in\\": { + \\"value\\": 28804250 + }, + \\"bytes_out\\": { + \\"value\\": 482982 + }, + \\"location\\": { + \\"doc_count\\": 14, + \\"top_geo\\": { + \\"hits\\": { + \\"total\\": { + \\"value\\": 14, + \\"relation\\": \\"eq\\" + }, + \\"max_score\\": 1, + \\"hits\\": [ + { + \\"_index\\": \\"filebeat-8.0.0-2019.06.19-000005\\", + \\"_type\\": \\"_doc\\", + \\"_id\\": \\"dd4fa2d4bd-692279846149410\\", + \\"_score\\": 1, + \\"_source\\": { + \\"source\\": { + \\"geo\\": { + \\"continent_name\\": \\"North America\\", + \\"region_iso_code\\": \\"US-PA\\", + \\"city_name\\": \\"Philadelphia\\", + \\"country_iso_code\\": \\"US\\", + \\"region_name\\": \\"Pennsylvania\\", + \\"location\\": { + \\"lon\\": -75.1534, + \\"lat\\": 39.9359 + } + } + } + } + } + ] + } + } + }, + \\"autonomous_system\\": { + \\"doc_count\\": 14, + \\"top_as\\": { + \\"hits\\": { + \\"total\\": { + \\"value\\": 14, + \\"relation\\": \\"eq\\" + }, + \\"max_score\\": 1, + \\"hits\\": [ + { + \\"_index\\": \\"filebeat-8.0.0-2019.06.19-000005\\", + \\"_type\\": \\"_doc\\", + \\"_id\\": \\"dd4fa2d4bd-692279846149410\\", + \\"_score\\": 1, + \\"_source\\": { + \\"source\\": { + \\"as\\": { + \\"number\\": 3356, + \\"organization\\": { + \\"name\\": \\"Level 3 Parent, LLC\\" + } + } + } + } + } + ] + } + } + }, + \\"domain\\": { + \\"doc_count_error_upper_bound\\": 0, + \\"sum_other_doc_count\\": 31, + \\"buckets\\": [ + { + \\"key\\": \\"test.6.com\\" + } + ] + } + }, + { + \\"key\\": \\"7.7.7.7\\", + \\"flows\\": { + \\"value\\": 1234567 + }, + \\"destination_ips\\": { + \\"value\\": 345345 + }, + \\"bytes_in\\": { + \\"value\\": 23032363 + }, + \\"bytes_out\\": { + \\"value\\": 400623 + }, + \\"location\\": { + \\"doc_count\\": 14, + \\"top_geo\\": { + \\"hits\\": { + \\"total\\": { + \\"value\\": 14, + \\"relation\\": \\"eq\\" + }, + \\"max_score\\": 1, + \\"hits\\": [ + { + \\"_index\\": \\"filebeat-8.0.0-2019.06.19-000005\\", + \\"_type\\": \\"_doc\\", + \\"_id\\": \\"dd4fa2d4bd-692279846149410\\", + \\"_score\\": 1, + \\"_source\\": { + \\"source\\": { + \\"geo\\": { + \\"continent_name\\": \\"North America\\", + \\"region_iso_code\\": \\"US-PA\\", + \\"city_name\\": \\"Philadelphia\\", + \\"country_iso_code\\": \\"US\\", + \\"region_name\\": \\"Pennsylvania\\", + \\"location\\": { + \\"lon\\": -75.1534, + \\"lat\\": 39.9359 + } + } + } + } + } + ] + } + } + }, + \\"autonomous_system\\": { + \\"doc_count\\": 14, + \\"top_as\\": { + \\"hits\\": { + \\"total\\": { + \\"value\\": 14, + \\"relation\\": \\"eq\\" + }, + \\"max_score\\": 1, + \\"hits\\": [ + { + \\"_index\\": \\"filebeat-8.0.0-2019.06.19-000005\\", + \\"_type\\": \\"_doc\\", + \\"_id\\": \\"dd4fa2d4bd-692279846149410\\", + \\"_score\\": 1, + \\"_source\\": { + \\"source\\": { + \\"as\\": { + \\"number\\": 3356, + \\"organization\\": { + \\"name\\": \\"Level 3 Parent, LLC\\" + } + } + } + } + } + ] + } + } + }, + \\"domain\\": { + \\"doc_count_error_upper_bound\\": 0, + \\"sum_other_doc_count\\": 0, + \\"buckets\\": [ + { + \\"key\\": \\"test.7.com\\" + } + ] + } + }, + { + \\"key\\": \\"8.8.8.8\\", + \\"flows\\": { + \\"value\\": 1234567 + }, + \\"destination_ips\\": { + \\"value\\": 345345 + }, + \\"bytes_in\\": { + \\"value\\": 21424889 + }, + \\"bytes_out\\": { + \\"value\\": 344357 + }, + \\"location\\": { + \\"doc_count\\": 14, + \\"top_geo\\": { + \\"hits\\": { + \\"total\\": { + \\"value\\": 14, + \\"relation\\": \\"eq\\" + }, + \\"max_score\\": 1, + \\"hits\\": [ + { + \\"_index\\": \\"filebeat-8.0.0-2019.06.19-000005\\", + \\"_type\\": \\"_doc\\", + \\"_id\\": \\"dd4fa2d4bd-692279846149410\\", + \\"_score\\": 1, + \\"_source\\": { + \\"source\\": { + \\"geo\\": { + \\"continent_name\\": \\"North America\\", + \\"region_iso_code\\": \\"US-PA\\", + \\"city_name\\": \\"Philadelphia\\", + \\"country_iso_code\\": \\"US\\", + \\"region_name\\": \\"Pennsylvania\\", + \\"location\\": { + \\"lon\\": -75.1534, + \\"lat\\": 39.9359 + } + } + } + } + } + ] + } + } + }, + \\"autonomous_system\\": { + \\"doc_count\\": 14, + \\"top_as\\": { + \\"hits\\": { + \\"total\\": { + \\"value\\": 14, + \\"relation\\": \\"eq\\" + }, + \\"max_score\\": 1, + \\"hits\\": [ + { + \\"_index\\": \\"filebeat-8.0.0-2019.06.19-000005\\", + \\"_type\\": \\"_doc\\", + \\"_id\\": \\"dd4fa2d4bd-692279846149410\\", + \\"_score\\": 1, + \\"_source\\": { + \\"source\\": { + \\"as\\": { + \\"number\\": 3356, + \\"organization\\": { + \\"name\\": \\"Level 3 Parent, LLC\\" + } + } + } + } + } + ] + } + } + }, + \\"domain\\": { + \\"buckets\\": [ + { + \\"key\\": \\"test.8.com\\" + } + ] + } + }, + { + \\"key\\": \\"9.9.9.9\\", + \\"flows\\": { + \\"value\\": 1234567 + }, + \\"destination_ips\\": { + \\"value\\": 345345 + }, + \\"bytes_in\\": { + \\"value\\": 19205000 + }, + \\"bytes_out\\": { + \\"value\\": 355663 + }, + \\"location\\": { + \\"doc_count\\": 14, + \\"top_geo\\": { + \\"hits\\": { + \\"total\\": { + \\"value\\": 14, + \\"relation\\": \\"eq\\" + }, + \\"max_score\\": 1, + \\"hits\\": [ + { + \\"_index\\": \\"filebeat-8.0.0-2019.06.19-000005\\", + \\"_type\\": \\"_doc\\", + \\"_id\\": \\"dd4fa2d4bd-692279846149410\\", + \\"_score\\": 1, + \\"_source\\": { + \\"source\\": { + \\"geo\\": { + \\"continent_name\\": \\"North America\\", + \\"region_iso_code\\": \\"US-PA\\", + \\"city_name\\": \\"Philadelphia\\", + \\"country_iso_code\\": \\"US\\", + \\"region_name\\": \\"Pennsylvania\\", + \\"location\\": { + \\"lon\\": -75.1534, + \\"lat\\": 39.9359 + } + } + } + } + } + ] + } + } + }, + \\"autonomous_system\\": { + \\"doc_count\\": 14, + \\"top_as\\": { + \\"hits\\": { + \\"total\\": { + \\"value\\": 14, + \\"relation\\": \\"eq\\" + }, + \\"max_score\\": 1, + \\"hits\\": [ + { + \\"_index\\": \\"filebeat-8.0.0-2019.06.19-000005\\", + \\"_type\\": \\"_doc\\", + \\"_id\\": \\"dd4fa2d4bd-692279846149410\\", + \\"_score\\": 1, + \\"_source\\": { + \\"source\\": { + \\"as\\": { + \\"number\\": 3356, + \\"organization\\": { + \\"name\\": \\"Level 3 Parent, LLC\\" + } + } + } + } + } + ] + } + } + }, + \\"domain\\": { + \\"buckets\\": [ + { + \\"key\\": \\"test.9.com\\" + } + ] + } + }, + { + \\"key\\": \\"10.10.10.10\\", + \\"flows\\": { + \\"value\\": 1234567 + }, + \\"destination_ips\\": { + \\"value\\": 345345 + }, + \\"bytes_in\\": { + \\"value\\": 11407633 + }, + \\"bytes_out\\": { + \\"value\\": 199360 + }, + \\"location\\": { + \\"doc_count\\": 14, + \\"top_geo\\": { + \\"hits\\": { + \\"total\\": { + \\"value\\": 14, + \\"relation\\": \\"eq\\" + }, + \\"max_score\\": 1, + \\"hits\\": [ + { + \\"_index\\": \\"filebeat-8.0.0-2019.06.19-000005\\", + \\"_type\\": \\"_doc\\", + \\"_id\\": \\"dd4fa2d4bd-692279846149410\\", + \\"_score\\": 1, + \\"_source\\": { + \\"source\\": { + \\"geo\\": { + \\"continent_name\\": \\"North America\\", + \\"region_iso_code\\": \\"US-PA\\", + \\"city_name\\": \\"Philadelphia\\", + \\"country_iso_code\\": \\"US\\", + \\"region_name\\": \\"Pennsylvania\\", + \\"location\\": { + \\"lon\\": -75.1534, + \\"lat\\": 39.9359 + } + } + } + } + } + ] + } + } + }, + \\"autonomous_system\\": { + \\"doc_count\\": 14, + \\"top_as\\": { + \\"hits\\": { + \\"total\\": { + \\"value\\": 14, + \\"relation\\": \\"eq\\" + }, + \\"max_score\\": 1, + \\"hits\\": [ + { + \\"_index\\": \\"filebeat-8.0.0-2019.06.19-000005\\", + \\"_type\\": \\"_doc\\", + \\"_id\\": \\"dd4fa2d4bd-692279846149410\\", + \\"_score\\": 1, + \\"_source\\": { + \\"source\\": { + \\"as\\": { + \\"number\\": 3356, + \\"organization\\": { + \\"name\\": \\"Level 3 Parent, LLC\\" + } + } + } + } + } + ] + } + } + }, + \\"domain\\": { + \\"buckets\\": [ + { + \\"key\\": \\"test.10.com\\" + } + ] + } + }, + { + \\"key\\": \\"11.11.11.11\\", + \\"flows\\": { + \\"value\\": 1234567 + }, + \\"destination_ips\\": { + \\"value\\": 345345 + }, + \\"bytes_in\\": { + \\"value\\": 11393327 + }, + \\"bytes_out\\": { + \\"value\\": 195914 + }, + \\"location\\": { + \\"doc_count\\": 14, + \\"top_geo\\": { + \\"hits\\": { + \\"total\\": { + \\"value\\": 14, + \\"relation\\": \\"eq\\" + }, + \\"max_score\\": 1, + \\"hits\\": [ + { + \\"_index\\": \\"filebeat-8.0.0-2019.06.19-000005\\", + \\"_type\\": \\"_doc\\", + \\"_id\\": \\"dd4fa2d4bd-692279846149410\\", + \\"_score\\": 1, + \\"_source\\": { + \\"source\\": { + \\"geo\\": { + \\"continent_name\\": \\"North America\\", + \\"region_iso_code\\": \\"US-PA\\", + \\"city_name\\": \\"Philadelphia\\", + \\"country_iso_code\\": \\"US\\", + \\"region_name\\": \\"Pennsylvania\\", + \\"location\\": { + \\"lon\\": -75.1534, + \\"lat\\": 39.9359 + } + } + } + } + } + ] + } + } + }, + \\"autonomous_system\\": { + \\"doc_count\\": 14, + \\"top_as\\": { + \\"hits\\": { + \\"total\\": { + \\"value\\": 14, + \\"relation\\": \\"eq\\" + }, + \\"max_score\\": 1, + \\"hits\\": [ + { + \\"_index\\": \\"filebeat-8.0.0-2019.06.19-000005\\", + \\"_type\\": \\"_doc\\", + \\"_id\\": \\"dd4fa2d4bd-692279846149410\\", + \\"_score\\": 1, + \\"_source\\": { + \\"source\\": { + \\"as\\": { + \\"number\\": 3356, + \\"organization\\": { + \\"name\\": \\"Level 3 Parent, LLC\\" + } + } + } + } + } + ] + } + } + }, + \\"domain\\": { + \\"buckets\\": [ + { + \\"key\\": \\"test.11.com\\" + } + ] + } + } + ] + } + } +}", + ], + }, + "pageInfo": Object { + "activePage": 0, + "fakeTotalCount": 50, + "showMorePagesIndicator": true, + }, + "totalCount": 545, +} +`; diff --git a/x-pack/legacy/plugins/siem/server/lib/network/elastic_adapter.test.ts b/x-pack/legacy/plugins/siem/server/lib/network/elastic_adapter.test.ts index c3bcfafac8757..542a2a0108a9a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/network/elastic_adapter.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/network/elastic_adapter.test.ts @@ -96,6 +96,36 @@ describe('Network Top N flow elasticsearch_adapter with FlowTarget=source', () = }); }); + describe('Unhappy Path - No geo data', () => { + const mockCallWithRequest = jest.fn(); + const mockNoGeoDataResponse = cloneDeep(mockResponse); + // sometimes bad things happen to good ecs + mockNoGeoDataResponse.aggregations[ + FlowTargetSourceDest.source + ].buckets[0].location.top_geo.hits.hits = []; + mockCallWithRequest.mockResolvedValue(mockNoGeoDataResponse); + const mockFramework: FrameworkAdapter = { + version: 'mock', + callWithRequest: mockCallWithRequest, + exposeStaticDir: jest.fn(), + getIndexPatternsService: jest.fn(), + getSavedObjectsService: jest.fn(), + registerGraphQLEndpoint: jest.fn(), + }; + jest.doMock('../framework', () => ({ + callWithRequest: mockCallWithRequest, + })); + + test('getNetworkTopNFlow', async () => { + const EsNetworkTopNFlow = new ElasticsearchNetworkAdapter(mockFramework); + const data: NetworkTopNFlowData = await EsNetworkTopNFlow.getNetworkTopNFlow( + mockRequest as FrameworkRequest, + mockOptions + ); + expect(data).toMatchSnapshot(); + }); + }); + describe('No pagination', () => { const mockNoPaginationResponse = cloneDeep(mockResponse); mockNoPaginationResponse.aggregations.top_n_flow_count.value = 10; diff --git a/x-pack/legacy/plugins/siem/server/lib/network/elasticsearch_adapter.ts b/x-pack/legacy/plugins/siem/server/lib/network/elasticsearch_adapter.ts index 5a871a3f9c9b4..eff5fba0c54d5 100644 --- a/x-pack/legacy/plugins/siem/server/lib/network/elasticsearch_adapter.ts +++ b/x-pack/legacy/plugins/siem/server/lib/network/elasticsearch_adapter.ts @@ -193,19 +193,20 @@ const getGeoItem = (result: NetworkTopNFlowBuckets): GeoItem | null => : null; const getAsItem = (result: NetworkTopNFlowBuckets): AutonomousSystemItem | null => - result.autonomous_system.top_as.hits.hits.length > 0 + result.autonomous_system.top_as.hits.hits.length > 0 && + result.autonomous_system.top_as.hits.hits[0]._source ? { number: getOr( null, `autonomous_system.top_as.hits.hits[0]._source.${ - Object.keys(result.location.top_geo.hits.hits[0]._source)[0] + Object.keys(result.autonomous_system.top_as.hits.hits[0]._source)[0] }.as.number`, result ), name: getOr( '', `autonomous_system.top_as.hits.hits[0]._source.${ - Object.keys(result.location.top_geo.hits.hits[0]._source)[0] + Object.keys(result.autonomous_system.top_as.hits.hits[0]._source)[0] }.as.organization.name`, result ), diff --git a/x-pack/legacy/plugins/siem/server/lib/types.ts b/x-pack/legacy/plugins/siem/server/lib/types.ts index d690954f88057..ad099b728db36 100644 --- a/x-pack/legacy/plugins/siem/server/lib/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/types.ts @@ -193,5 +193,16 @@ export interface AggregationRequest { }; }; }; + top_hits?: { + size?: number; + sort?: Array<{ + [aggSortField: string]: { + order: SortRequestDirection; + }; + }>; + _source: { + includes: string[]; + }; + }; }; } diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/steps/step_logistics.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/steps/step_logistics.tsx index 80240ae0e7718..7c3036b3d79c2 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/steps/step_logistics.tsx +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/steps/step_logistics.tsx @@ -476,12 +476,12 @@ export const PolicyStepLogistics: React.FunctionComponent = ({ -

+

-

+
diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/steps/step_retention.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/steps/step_retention.tsx index 3944cd52c7ee2..c88cbd2736df6 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/steps/step_retention.tsx +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/steps/step_retention.tsx @@ -206,12 +206,12 @@ export const PolicyStepRetention: React.FunctionComponent = ({ -

+

-

+
diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/steps/step_review.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/steps/step_review.tsx index 52667ee7ebc87..3ddbcd94009ac 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/steps/step_review.tsx +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/steps/step_review.tsx @@ -362,12 +362,12 @@ export const PolicyStepReview: React.FunctionComponent = ({ return ( -

+

-

+
= ({ -

+

-

+
diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/components/repository_form/step_one.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/components/repository_form/step_one.tsx index d8cf346dcdc43..8f6b465b2fe5f 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/components/repository_form/step_one.tsx +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/components/repository_form/step_one.tsx @@ -88,12 +88,12 @@ export const RepositoryFormStepOne: React.FunctionComponent = ({ -

+

-

+ } description={ @@ -227,12 +227,12 @@ export const RepositoryFormStepOne: React.FunctionComponent = ({ return ( -

+

-

+
@@ -274,12 +274,12 @@ export const RepositoryFormStepOne: React.FunctionComponent = ({ -

+

-

+ } description={ diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/components/restore_snapshot_form/steps/step_logistics.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/components/restore_snapshot_form/steps/step_logistics.tsx index 8a0d8039bb7cd..f5a3180adbd6e 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/components/restore_snapshot_form/steps/step_logistics.tsx +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/components/restore_snapshot_form/steps/step_logistics.tsx @@ -96,12 +96,12 @@ export const RestoreSnapshotStepLogistics: React.FunctionComponent = -

+

-

+
diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/components/restore_snapshot_form/steps/step_review.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/components/restore_snapshot_form/steps/step_review.tsx index 65a3918d29940..92b0ee48fef01 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/components/restore_snapshot_form/steps/step_review.tsx +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/components/restore_snapshot_form/steps/step_review.tsx @@ -350,12 +350,12 @@ export const RestoreSnapshotStepReview: React.FunctionComponent = ({ return ( -

+

-

+
= ( -

+

-

+
diff --git a/x-pack/legacy/plugins/spaces/public/views/management/page_routes.tsx b/x-pack/legacy/plugins/spaces/public/views/management/page_routes.tsx index 66cdb0d276e94..1f0afc706c3f0 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/page_routes.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/page_routes.tsx @@ -8,7 +8,6 @@ import template from 'plugins/spaces/views/management/template.html'; import { SpacesNavState } from 'plugins/spaces/views/nav_control'; import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; -import 'ui/autoload/styles'; import { I18nContext } from 'ui/i18n'; // @ts-ignore import routes from 'ui/routes'; diff --git a/x-pack/legacy/plugins/spaces/public/views/space_selector/index.tsx b/x-pack/legacy/plugins/spaces/public/views/space_selector/index.tsx index 8c650fa778bdd..6bb0aa4749048 100644 --- a/x-pack/legacy/plugins/spaces/public/views/space_selector/index.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/space_selector/index.tsx @@ -7,7 +7,6 @@ import { SpacesManager } from 'plugins/spaces/lib/spaces_manager'; // @ts-ignore import template from 'plugins/spaces/views/space_selector/space_selector.html'; -import 'ui/autoload/styles'; import chrome from 'ui/chrome'; import { I18nContext } from 'ui/i18n'; // @ts-ignore diff --git a/x-pack/legacy/plugins/task_manager/task_manager.test.ts b/x-pack/legacy/plugins/task_manager/task_manager.test.ts index 2bcfdb2f4d4e3..8592ff31d700f 100644 --- a/x-pack/legacy/plugins/task_manager/task_manager.test.ts +++ b/x-pack/legacy/plugins/task_manager/task_manager.test.ts @@ -7,11 +7,11 @@ import _ from 'lodash'; import sinon from 'sinon'; import { TaskManager, claimAvailableTasks } from './task_manager'; -import { SavedObjectsClientMock } from 'src/core/server/mocks'; +import { savedObjectsClientMock } from 'src/core/server/mocks'; import { SavedObjectsSerializer, SavedObjectsSchema } from 'src/core/server'; import { mockLogger } from './test_utils'; -const savedObjectsClient = SavedObjectsClientMock.create(); +const savedObjectsClient = savedObjectsClientMock.create(); const serializer = new SavedObjectsSerializer(new SavedObjectsSchema()); describe('TaskManager', () => { diff --git a/x-pack/legacy/plugins/task_manager/task_store.test.ts b/x-pack/legacy/plugins/task_manager/task_store.test.ts index 65b49820d6e6c..9779dc5efd28b 100644 --- a/x-pack/legacy/plugins/task_manager/task_store.test.ts +++ b/x-pack/legacy/plugins/task_manager/task_store.test.ts @@ -10,7 +10,7 @@ import uuid from 'uuid'; import { TaskDictionary, TaskDefinition, TaskInstance, TaskStatus } from './task'; import { FetchOpts, StoreOpts, OwnershipClaimingOpts, TaskStore } from './task_store'; import { mockLogger } from './test_utils'; -import { SavedObjectsClientMock } from 'src/core/server/mocks'; +import { savedObjectsClientMock } from 'src/core/server/mocks'; import { SavedObjectsSerializer, SavedObjectsSchema, SavedObjectAttributes } from 'src/core/server'; const taskDefinitions: TaskDictionary = { @@ -31,7 +31,7 @@ const taskDefinitions: TaskDictionary = { }, }; -const savedObjectsClient = SavedObjectsClientMock.create(); +const savedObjectsClient = savedObjectsClientMock.create(); const serializer = new SavedObjectsSerializer(new SavedObjectsSchema()); beforeEach(() => jest.resetAllMocks()); diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/aggregation_list/popover_form.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/aggregation_list/popover_form.tsx index e7f79b240d81a..65dd8a34330a5 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/aggregation_list/popover_form.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/aggregation_list/popover_form.tsx @@ -142,7 +142,7 @@ export const PopoverForm: React.SFC = ({ {isUnsupportedAgg && ( = ({ = React.memo( try { const resp = await api.createTransform(transformId, transformConfig); - - if (resp.errors !== undefined) { - if (Array.isArray(resp.errors) && resp.errors.length === 1) { + if (resp.errors !== undefined && Array.isArray(resp.errors)) { + if (resp.errors.length === 1) { throw resp.errors[0]; } - throw resp.errors; + if (resp.errors.length > 1) { + throw resp.errors; + } } toastNotifications.addSuccess( diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx index 49017ac9e3c02..6cde57fc2316d 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx @@ -591,6 +591,7 @@ export const StepDefineForm: SFC = React.memo(({ overrides = {}, onChange setOptions={{ fontSize: '12px', }} + theme="textmate" aria-label={i18n.translate( 'xpack.transform.stepDefineForm.advancedSourceEditorAriaLabel', { @@ -751,6 +752,7 @@ export const StepDefineForm: SFC = React.memo(({ overrides = {}, onChange setOptions={{ fontSize: '12px', }} + theme="textmate" aria-label={i18n.translate( 'xpack.transform.stepDefineForm.advancedEditorAriaLabel', { diff --git a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/__snapshots__/expanded_row_json_pane.test.tsx.snap b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/__snapshots__/expanded_row_json_pane.test.tsx.snap index f185579fd9e4c..0d4a80a94ee51 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/__snapshots__/expanded_row_json_pane.test.tsx.snap +++ b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/__snapshots__/expanded_row_json_pane.test.tsx.snap @@ -15,6 +15,7 @@ exports[`Transform: Transform List Expanded Row Minimal "width": "100%", } } + theme="textmate" value="{ \\"id\\": \\"fq_date_histogram_1m_1441\\", \\"source\\": { diff --git a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_json_pane.tsx b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_json_pane.tsx index d24fc19f216a3..416d93007daba 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_json_pane.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_json_pane.tsx @@ -28,6 +28,7 @@ export const ExpandedRowJsonPane: SFC = ({ json }) => { readOnly={true} mode="json" style={{ width: '100%' }} + theme="textmate" />   diff --git a/x-pack/legacy/plugins/uptime/common/constants/chart_format_limits.ts b/x-pack/legacy/plugins/uptime/common/constants/chart_format_limits.ts index 1e691b45e1792..f291450ab2a7a 100644 --- a/x-pack/legacy/plugins/uptime/common/constants/chart_format_limits.ts +++ b/x-pack/legacy/plugins/uptime/common/constants/chart_format_limits.ts @@ -4,8 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ +const MINUTE = 1000 * 60; +const HOUR = MINUTE * 60; const DAY = 24 * 60 * 60 * 1000; const WEEK = DAY * 7; +const MONTH = WEEK * 4; /** * These contsants are used by the charting code to determine @@ -14,9 +17,10 @@ const WEEK = DAY * 7; */ export const CHART_FORMAT_LIMITS = { DAY, - FIFTEEN_DAYS: 1000 * 60 * 60 * 24 * 15, - EIGHT_MINUTES: 1000 * 60 * 8, + EIGHT_MINUTES: MINUTE * 8, FOUR_YEARS: 4 * 12 * 4 * WEEK, - THIRTY_SIX_HOURS: 1000 * 60 * 60 * 36, + THIRTY_SIX_HOURS: HOUR * 36, THREE_WEEKS: WEEK * 3, + SIX_MONTHS: MONTH * 7, + NINE_DAYS: DAY * 9, }; diff --git a/x-pack/legacy/plugins/uptime/common/domain_types/monitors.ts b/x-pack/legacy/plugins/uptime/common/domain_types/monitors.ts index 7f5699eb7e8a4..296df279b8eec 100644 --- a/x-pack/legacy/plugins/uptime/common/domain_types/monitors.ts +++ b/x-pack/legacy/plugins/uptime/common/domain_types/monitors.ts @@ -4,7 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ +import { HistogramDataPoint } from '../graphql/types'; + export interface UMGqlRange { dateRangeStart: string; dateRangeEnd: string; } + +export interface HistogramResult { + histogram: HistogramDataPoint[]; + interval: number; +} diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/integration_group.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/integration_group.test.tsx.snap index f07d0171b22ad..bab69a6de9708 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/integration_group.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/integration_group.test.tsx.snap @@ -7,7 +7,7 @@ exports[`IntegrationGroup will not display APM links when APM is unavailable 1`] @@ -15,7 +15,7 @@ exports[`IntegrationGroup will not display APM links when APM is unavailable 1`] @@ -23,7 +23,7 @@ exports[`IntegrationGroup will not display APM links when APM is unavailable 1`] @@ -31,7 +31,7 @@ exports[`IntegrationGroup will not display APM links when APM is unavailable 1`] @@ -39,7 +39,7 @@ exports[`IntegrationGroup will not display APM links when APM is unavailable 1`] @@ -47,7 +47,7 @@ exports[`IntegrationGroup will not display APM links when APM is unavailable 1`] @@ -71,7 +71,7 @@ exports[`IntegrationGroup will not display infra links when infra is unavailable @@ -79,7 +79,7 @@ exports[`IntegrationGroup will not display infra links when infra is unavailable @@ -87,7 +87,7 @@ exports[`IntegrationGroup will not display infra links when infra is unavailable @@ -111,7 +111,7 @@ exports[`IntegrationGroup will not display logging links when logging is unavail @@ -119,7 +119,7 @@ exports[`IntegrationGroup will not display logging links when logging is unavail @@ -127,7 +127,7 @@ exports[`IntegrationGroup will not display logging links when logging is unavail diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/charts/donut_chart.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/charts/donut_chart.tsx index 3ae6f349d3997..76c3bd5d4c50d 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/charts/donut_chart.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/charts/donut_chart.tsx @@ -25,6 +25,10 @@ export const DonutChart = ({ height, down, up, width }: DonutChartProps) => { colors: { danger, gray }, } = useContext(UptimeSettingsContext); + let upCount = up; + if (up === 0 && down === 0) { + upCount = 1; + } useEffect(() => { if (chartElement.current !== null) { // we must remove any existing paths before painting @@ -50,7 +54,7 @@ export const DonutChart = ({ height, down, up, width }: DonutChartProps) => { .data( // @ts-ignore pie generator expects param of type number[], but only works with // output of d3.entries, which is like Array<{ key: string, value: number }> - pieGenerator(d3.entries({ up, down })) + pieGenerator(d3.entries({ up: upCount, down })) ) .enter() .append('path') @@ -64,7 +68,7 @@ export const DonutChart = ({ height, down, up, width }: DonutChartProps) => { ) .attr('fill', (d: any) => color(d.data.key)); } - }, [chartElement.current, up, down]); + }, [chartElement.current, upCount, down]); return ( diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/charts/snapshot_histogram.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/charts/snapshot_histogram.tsx index e39adc2868c84..a5d2d9cfc27f2 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/charts/snapshot_histogram.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/charts/snapshot_histogram.tsx @@ -20,13 +20,13 @@ import React, { useContext } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import moment from 'moment'; import styled from 'styled-components'; -import { HistogramDataPoint } from '../../../../common/graphql/types'; import { getColorsMap } from './get_colors_map'; import { getChartDateLabel } from '../../../lib/helper'; import { withUptimeGraphQL, UptimeGraphQLQueryProps } from '../../higher_order'; import { snapshotHistogramQuery } from '../../../queries/snapshot_histogram_query'; import { ChartWrapper } from './chart_wrapper'; import { UptimeSettingsContext } from '../../../contexts'; +import { HistogramResult } from '../../../../common/domain_types'; const SnapshotHistogramWrapper = styled.div` margin-left: 120px; @@ -56,7 +56,7 @@ export interface SnapshotHistogramProps { } interface SnapshotHistogramQueryResult { - histogram?: HistogramDataPoint[]; + queryResult?: HistogramResult; } type Props = UptimeGraphQLQueryProps & SnapshotHistogramProps; @@ -68,7 +68,7 @@ export const SnapshotHistogramComponent = ({ loading = false, height, }: Props) => { - if (!data || !data.histogram) + if (!data || !data.queryResult) /** * TODO: the Fragment, EuiTitle, and EuiPanel should be extracted to a dumb component * that we can reuse in the subsequent return statement at the bottom of this function. @@ -107,7 +107,9 @@ export const SnapshotHistogramComponent = ({ ); - const { histogram } = data; + const { + queryResult: { histogram, interval }, + } = data; const { colors: { danger, gray }, @@ -145,7 +147,14 @@ export const SnapshotHistogramComponent = ({ })} > - + setSearchQuery(query)} placeholder={ isLoading @@ -98,6 +100,7 @@ export const FilterPopover = ({ {item} ))} + {id === 'location' && items.length === 0 && } ); }; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/integration_group.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/integration_group.tsx index 2dd3d24a3cd8d..da66235e37f1a 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/integration_group.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/integration_group.tsx @@ -87,7 +87,7 @@ export const IntegrationGroup = ({ } )} href={getInfraIpHref(summary, basePath)} - iconType="infraApp" + iconType="metricsApp" message={i18n.translate( 'xpack.uptime.monitorList.infraIntegrationAction.ip.message', { @@ -116,7 +116,7 @@ export const IntegrationGroup = ({ } )} href={getInfraKubernetesHref(summary, basePath)} - iconType="infraApp" + iconType="metricsApp" message={i18n.translate( 'xpack.uptime.monitorList.infraIntegrationAction.kubernetes.message', { @@ -145,7 +145,7 @@ export const IntegrationGroup = ({ } )} href={getInfraContainerHref(summary, basePath)} - iconType="infraApp" + iconType="metricsApp" message={i18n.translate( 'xpack.uptime.monitorList.infraIntegrationAction.container.message', { @@ -177,7 +177,7 @@ export const IntegrationGroup = ({ } )} href={getLoggingIpHref(summary, basePath)} - iconType="loggingApp" + iconType="logsApp" message={i18n.translate( 'xpack.uptime.monitorList.loggingIntegrationAction.ip.message', { @@ -200,7 +200,7 @@ export const IntegrationGroup = ({ } )} href={getLoggingKubernetesHref(summary, basePath)} - iconType="loggingApp" + iconType="logsApp" message={i18n.translate( 'xpack.uptime.monitorList.loggingIntegrationAction.kubernetes.message', { @@ -227,7 +227,7 @@ export const IntegrationGroup = ({ } )} href={getLoggingContainerHref(summary, basePath)} - iconType="loggingApp" + iconType="logsApp" message={i18n.translate( 'xpack.uptime.monitorList.loggingIntegrationAction.container.message', { diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/integration_link.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/integration_link.tsx index 63a3053afc601..a545cd7c42927 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/integration_link.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/integration_link.tsx @@ -11,7 +11,7 @@ import { i18n } from '@kbn/i18n'; interface IntegrationLinkProps { ariaLabel: string; href: string | undefined; - iconType: 'apmApp' | 'infraApp' | 'loggingApp'; + iconType: 'apmApp' | 'metricsApp' | 'logsApp'; message: string; tooltipContent: string; } diff --git a/x-pack/legacy/plugins/uptime/public/lib/helper/charts/__tests__/get_label_format.test.ts b/x-pack/legacy/plugins/uptime/public/lib/helper/charts/__tests__/get_label_format.test.ts index c435e5acc2f28..bdf0832a98fdf 100644 --- a/x-pack/legacy/plugins/uptime/public/lib/helper/charts/__tests__/get_label_format.test.ts +++ b/x-pack/legacy/plugins/uptime/public/lib/helper/charts/__tests__/get_label_format.test.ts @@ -24,7 +24,7 @@ describe('getChartLabelFormatter', () => { it('creates a label with month/day and hour/minute for time between 36 hours and 4 days', () => { // Sun, 15 Jul 2001 17:53:10 GMT -> Thu, 19 Jul 2001 17:52:59 GMT - expect(getChartDateLabel(995219590000, 995565179000)).toBe('MM-dd HH:mm'); + expect(getChartDateLabel(995219590000, 995565179000)).toBe('MM-DD HH:mm'); }); it('creates a format without day/month string for delta within same day local time', () => { @@ -34,17 +34,25 @@ describe('getChartLabelFormatter', () => { it('creates a format with date/month string for delta crossing dates', () => { // Wed, 18 Jul 2001 11:06:19 GMT -> Thu, 19 Jul 2001 17:52:59 GMT - expect(getChartDateLabel(995454379000, 995565179000)).toBe('MM-dd HH:mm'); + expect(getChartDateLabel(995454379000, 995565179000)).toBe('MM-DD HH:mm'); }); it('creates a format with only month/day for delta between to eight days and two weeks', () => { // Sun, 01 Jul 2001 23:28:15 GMT -> Thu, 19 Jul 2001 17:52:59 GMT - expect(getChartDateLabel(994030095000, 995565179000)).toBe('MM-dd'); + expect(getChartDateLabel(994030095000, 995565179000)).toBe('MM-DD'); }); - it('creates a format with the year/month for range exceeding a week', () => { + it('creates a format with the date and hour/min for 36h < range < 9d', () => { + expect(getChartDateLabel(1003752000000, 1004270400000)).toBe('MM-DD HH:mm'); + }); + + it('creates a format with month/day for range between nine days and three weeks', () => { + expect(getChartDateLabel(1003752000000, 1004875200000)).toBe('MM-DD'); + }); + + it('creates a format with the year/month/day for range exceeding three weeks', () => { // Sun, 15 Jul 2001 12:27:59 GMT -> Fri, 28 Dec 2001 18:46:19 GMT - expect(getChartDateLabel(995200079000, 1009565179000)).toBe('yyyy-MM'); + expect(getChartDateLabel(995200079000, 1009565179000)).toBe('YYYY-MM-DD'); }); it('creates a format of only year for timespan > 4 years', () => { diff --git a/x-pack/legacy/plugins/uptime/public/lib/helper/charts/get_chart_date_label.ts b/x-pack/legacy/plugins/uptime/public/lib/helper/charts/get_chart_date_label.ts index 3533f2667b66c..126b1d85f749f 100644 --- a/x-pack/legacy/plugins/uptime/public/lib/helper/charts/get_chart_date_label.ts +++ b/x-pack/legacy/plugins/uptime/public/lib/helper/charts/get_chart_date_label.ts @@ -34,7 +34,7 @@ export const getChartDateLabel = (dateRangeStart: number, dateRangeEnd: number) delta < CHART_FORMAT_LIMITS.THIRTY_SIX_HOURS && !isWithinCurrentDate(dateRangeStart, dateRangeEnd) ) { - formatString = 'MM-dd '; + formatString = 'MM-DD '; } return formatString + getLabelFormat(delta); }; diff --git a/x-pack/legacy/plugins/uptime/public/lib/helper/charts/get_label_format.ts b/x-pack/legacy/plugins/uptime/public/lib/helper/charts/get_label_format.ts index ad3e04f403640..668147fee8055 100644 --- a/x-pack/legacy/plugins/uptime/public/lib/helper/charts/get_label_format.ts +++ b/x-pack/legacy/plugins/uptime/public/lib/helper/charts/get_label_format.ts @@ -7,11 +7,12 @@ import { CHART_FORMAT_LIMITS } from '../../../../common/constants'; const { - FIFTEEN_DAYS, EIGHT_MINUTES, FOUR_YEARS, THIRTY_SIX_HOURS, THREE_WEEKS, + SIX_MONTHS, + NINE_DAYS, } = CHART_FORMAT_LIMITS; /** @@ -30,16 +31,20 @@ const dateStops: Array<{ key: number; value: string }> = [ value: 'HH:mm', }, { - key: FIFTEEN_DAYS, - value: 'MM-dd HH:mm', + key: NINE_DAYS, + value: 'MM-DD HH:mm', }, { key: THREE_WEEKS, - value: 'MM-dd', + value: 'MM-DD', + }, + { + key: SIX_MONTHS, + value: 'YYYY-MM-DD', }, { key: FOUR_YEARS, - value: 'yyyy-MM', + value: 'YYYY-MM', }, ]; diff --git a/x-pack/legacy/plugins/uptime/public/queries/snapshot_histogram_query.ts b/x-pack/legacy/plugins/uptime/public/queries/snapshot_histogram_query.ts index 37f7178d23630..7eb56ea4e9dd1 100644 --- a/x-pack/legacy/plugins/uptime/public/queries/snapshot_histogram_query.ts +++ b/x-pack/legacy/plugins/uptime/public/queries/snapshot_histogram_query.ts @@ -14,18 +14,21 @@ export const snapshotHistogramQueryString = ` $monitorId: String $statusFilter: String ) { - histogram: getSnapshotHistogram( + queryResult: getSnapshotHistogram( dateRangeStart: $dateRangeStart dateRangeEnd: $dateRangeEnd filters: $filters statusFilter: $statusFilter monitorId: $monitorId ) { + histogram { upCount downCount x x0 y + } + interval } } `; diff --git a/x-pack/legacy/plugins/uptime/server/graphql/monitors/resolvers.ts b/x-pack/legacy/plugins/uptime/server/graphql/monitors/resolvers.ts index 287e80a6291ca..96a386b6a6848 100644 --- a/x-pack/legacy/plugins/uptime/server/graphql/monitors/resolvers.ts +++ b/x-pack/legacy/plugins/uptime/server/graphql/monitors/resolvers.ts @@ -17,11 +17,11 @@ import { MonitorPageTitle, Ping, Snapshot, - HistogramDataPoint, GetSnapshotHistogramQueryArgs, } from '../../../common/graphql/types'; import { UMServerLibs } from '../../lib/lib'; import { CreateUMGraphQLResolvers, UMContext } from '../types'; +import { HistogramResult } from '../../../common/domain_types'; export type UMSnapshotResolver = UMResolver< Snapshot | Promise, @@ -61,7 +61,7 @@ export type UMGetMontiorPageTitleResolver = UMResolver< >; export type UMGetSnapshotHistogram = UMResolver< - HistogramDataPoint[] | Promise, + HistogramResult | Promise, any, GetSnapshotHistogramQueryArgs, UMContext @@ -101,7 +101,7 @@ export const createMonitorsResolvers: CreateUMGraphQLResolvers = ( resolver, { dateRangeStart, dateRangeEnd, filters, monitorId, statusFilter }, { req } - ): Promise { + ): Promise { return await libs.pings.getPingHistogram( req, dateRangeStart, diff --git a/x-pack/legacy/plugins/uptime/server/graphql/monitors/schema.gql.ts b/x-pack/legacy/plugins/uptime/server/graphql/monitors/schema.gql.ts index d406be2e8b15b..97dcbd12fff2e 100644 --- a/x-pack/legacy/plugins/uptime/server/graphql/monitors/schema.gql.ts +++ b/x-pack/legacy/plugins/uptime/server/graphql/monitors/schema.gql.ts @@ -120,6 +120,11 @@ export const monitorsSchema = gql` monitors: [LatestMonitor!] } + type HistogramResult { + histogram: [HistogramDataPoint]! + interval: UnsignedInteger! + } + type MonitorPageTitle { id: String! url: String @@ -147,7 +152,7 @@ export const monitorsSchema = gql` filters: String statusFilter: String monitorId: String - ): [HistogramDataPoint!]! + ): HistogramResult getMonitorChartsData( monitorId: String! diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/enrich_monitor_groups.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/enrich_monitor_groups.ts index 2518264292cbd..6b594d8b49118 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/enrich_monitor_groups.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/enrich_monitor_groups.ts @@ -6,7 +6,7 @@ import { get, sortBy } from 'lodash'; import { QueryContext } from '../elasticsearch_monitor_states_adapter'; -import { getHistogramInterval } from '../../../helper'; +import { getHistogramIntervalFormatted } from '../../../helper'; import { INDEX_NAMES, STATES } from '../../../../../common/constants'; import { MonitorSummary, @@ -324,7 +324,7 @@ const getHistogramForMonitors = async ( histogram: { date_histogram: { field: '@timestamp', - fixed_interval: getHistogramInterval( + fixed_interval: getHistogramIntervalFormatted( queryContext.dateRangeStart, queryContext.dateRangeEnd ), diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/__tests__/__snapshots__/elasticsearch_monitors_adapter.test.ts.snap b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/__tests__/__snapshots__/elasticsearch_monitors_adapter.test.ts.snap index 75b19d7381a62..99349f42d5750 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/__tests__/__snapshots__/elasticsearch_monitors_adapter.test.ts.snap +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/__tests__/__snapshots__/elasticsearch_monitors_adapter.test.ts.snap @@ -221,6 +221,10 @@ Object { "x": 1568412432000, "y": 1972483.25, }, + Object { + "x": 1568412468000, + "y": 1020490, + }, ], "name": "us-east-2", }, @@ -302,6 +306,10 @@ Object { "x": 1568412432000, "y": 1543307.5, }, + Object { + "x": 1568412468000, + "y": null, + }, ], "name": "us-west-4", }, @@ -421,6 +429,12 @@ Object { "up": null, "x": 1568412432000, }, + Object { + "down": null, + "total": 1, + "up": null, + "x": 1568412468000, + }, ], "statusMaxCount": 0, } diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/elasticsearch_monitors_adapter.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/elasticsearch_monitors_adapter.ts index c14e3dab987d7..f2d84d149344b 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/elasticsearch_monitors_adapter.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/elasticsearch_monitors_adapter.ts @@ -13,12 +13,7 @@ import { Ping, LocationDurationLine, } from '../../../../common/graphql/types'; -import { - dropLatestBucket, - getFilterClause, - getHistogramInterval, - parseFilterQuery, -} from '../../helper'; +import { getFilterClause, parseFilterQuery, getHistogramIntervalFormatted } from '../../helper'; import { DatabaseAdapter } from '../database'; import { UMMonitorsAdapter } from './adapter_types'; @@ -80,7 +75,7 @@ export class ElasticsearchMonitorsAdapter implements UMMonitorsAdapter { timeseries: { date_histogram: { field: '@timestamp', - fixed_interval: getHistogramInterval(dateRangeStart, dateRangeEnd), + fixed_interval: getHistogramIntervalFormatted(dateRangeStart, dateRangeEnd), min_doc_count: 0, }, aggs: { @@ -102,10 +97,7 @@ export class ElasticsearchMonitorsAdapter implements UMMonitorsAdapter { const result = await this.database.search(request, params); - const dateHistogramBuckets = dropLatestBucket( - get(result, 'aggregations.timeseries.buckets', []) - ); - + const dateHistogramBuckets = get(result, 'aggregations.timeseries.buckets', []); /** * The code below is responsible for formatting the aggregation data we fetched above in a way * that the chart components used by the client understands. diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/pings/__tests__/__snapshots__/elasticsearch_pings_adapter.test.ts.snap b/x-pack/legacy/plugins/uptime/server/lib/adapters/pings/__tests__/__snapshots__/elasticsearch_pings_adapter.test.ts.snap index a7526739c95ac..b73595d539e93 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/pings/__tests__/__snapshots__/elasticsearch_pings_adapter.test.ts.snap +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/pings/__tests__/__snapshots__/elasticsearch_pings_adapter.test.ts.snap @@ -1,82 +1,127 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`ElasticsearchPingsAdapter class getPingHistogram handles simple_text_query without issues 1`] = ` -Array [ - Object { - "downCount": 1, - "key": 1, - "upCount": 2, - "x": 2, - "x0": 1, - "y": 1, - }, - Object { - "downCount": 2, - "key": 2, - "upCount": 1, - "x": 3, - "x0": 2, - "y": 1, - }, -] +Object { + "histogram": Array [ + Object { + "downCount": 1, + "upCount": 2, + "x": 1, + "y": 1, + }, + Object { + "downCount": 2, + "upCount": 1, + "x": 2, + "y": 1, + }, + Object { + "downCount": 1, + "upCount": 3, + "x": 3, + "y": 1, + }, + ], + "interval": 36000, +} `; exports[`ElasticsearchPingsAdapter class getPingHistogram handles status + additional user queries 1`] = ` -Array [ - Object { - "downCount": 1, - "key": 1, - "upCount": 0, - "x": 2, - "x0": 1, - "y": 1, - }, - Object { - "downCount": 2, - "key": 2, - "upCount": 0, - "x": 3, - "x0": 2, - "y": 1, - }, -] +Object { + "histogram": Array [ + Object { + "downCount": 1, + "upCount": 0, + "x": 1, + "y": 1, + }, + Object { + "downCount": 2, + "upCount": 0, + "x": 2, + "y": 1, + }, + Object { + "downCount": 1, + "upCount": 0, + "x": 3, + "y": 1, + }, + ], + "interval": 5609564928000, +} `; exports[`ElasticsearchPingsAdapter class getPingHistogram returns a down-filtered array for when filtered by down status 1`] = ` -Array [ - Object { - "downCount": 1, - "key": 1, - "upCount": 0, - "x": 2, - "x0": 1, - "y": 1, - }, -] +Object { + "histogram": Array [ + Object { + "downCount": 1, + "upCount": 0, + "x": 1, + "y": 1, + }, + Object { + "downCount": undefined, + "upCount": 0, + "x": 2, + "y": 1, + }, + ], + "interval": 5609564928000, +} `; exports[`ElasticsearchPingsAdapter class getPingHistogram returns a down-filtered array for when filtered by up status 1`] = ` -Array [ - Object { - "downCount": 0, - "key": 1, - "upCount": 2, - "x": 2, - "x0": 1, - "y": 1, - }, -] +Object { + "histogram": Array [ + Object { + "downCount": 0, + "upCount": 2, + "x": 1, + "y": 1, + }, + Object { + "downCount": 0, + "upCount": 2, + "x": 2, + "y": 1, + }, + ], + "interval": 5609564928000, +} +`; + +exports[`ElasticsearchPingsAdapter class getPingHistogram returns a single bucket if array has 1 1`] = ` +Object { + "histogram": Array [ + Object { + "downCount": 1, + "upCount": 2, + "x": 1, + "y": 1, + }, + ], + "interval": 36000, +} `; exports[`ElasticsearchPingsAdapter class getPingHistogram returns expected result for no status filter 1`] = ` -Array [ - Object { - "downCount": 1, - "key": 1, - "upCount": 2, - "x": 2, - "x0": 1, - "y": 1, - }, -] +Object { + "histogram": Array [ + Object { + "downCount": 1, + "upCount": 2, + "x": 1, + "y": 1, + }, + Object { + "downCount": undefined, + "upCount": 2, + "x": 2, + "y": 1, + }, + ], + "interval": 36000, +} `; diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/pings/__tests__/elasticsearch_pings_adapter.test.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/pings/__tests__/elasticsearch_pings_adapter.test.ts index ec414cda7d811..5c481cd147c61 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/pings/__tests__/elasticsearch_pings_adapter.test.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/pings/__tests__/elasticsearch_pings_adapter.test.ts @@ -88,7 +88,7 @@ describe('ElasticsearchPingsAdapter class', () => { }); describe('getPingHistogram', () => { - it('returns an empty array for <= 1 bucket', async () => { + it('returns a single bucket if array has 1', async () => { expect.assertions(2); const search = jest.fn(); search.mockReturnValue({ @@ -116,7 +116,7 @@ describe('ElasticsearchPingsAdapter class', () => { const pingAdapter = new ElasticsearchPingsAdapter(pingDatabase); const result = await pingAdapter.getPingHistogram(serverRequest, 'now-15m', 'now', null); expect(pingDatabase.search).toHaveBeenCalledTimes(1); - expect(result).toEqual([]); + expect(result).toMatchSnapshot(); }); it('returns expected result for no status filter', async () => { diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/pings/adapter_types.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/pings/adapter_types.ts index bb26e04f2fc9e..1e0cf7ec40646 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/pings/adapter_types.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/pings/adapter_types.ts @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { DocCount, HistogramDataPoint, Ping, PingResults } from '../../../../common/graphql/types'; +import { DocCount, Ping, PingResults } from '../../../../common/graphql/types'; +import { HistogramResult } from '../../../../common/domain_types'; export interface UMPingsAdapter { getAll( @@ -33,7 +34,7 @@ export interface UMPingsAdapter { filters?: string | null, monitorId?: string | null, statusFilter?: string | null - ): Promise; + ): Promise; getDocCount(request: any): Promise; } diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/pings/elasticsearch_pings_adapter.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/pings/elasticsearch_pings_adapter.ts index 822441449a774..cad8b412f3e58 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/pings/elasticsearch_pings_adapter.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/pings/elasticsearch_pings_adapter.ts @@ -6,17 +6,12 @@ import { get } from 'lodash'; import { INDEX_NAMES } from '../../../../common/constants'; -import { - DocCount, - HistogramDataPoint, - HttpBody, - Ping, - PingResults, -} from '../../../../common/graphql/types'; -import { formatEsBucketsForHistogram, parseFilterQuery, getFilterClause } from '../../helper'; +import { DocCount, HttpBody, Ping, PingResults } from '../../../../common/graphql/types'; +import { parseFilterQuery, getFilterClause, getHistogramIntervalFormatted } from '../../helper'; import { DatabaseAdapter, HistogramQueryResult } from '../database'; import { UMPingsAdapter } from './adapter_types'; import { getHistogramInterval } from '../../helper/get_histogram_interval'; +import { HistogramResult } from '../../../../common/domain_types'; export class ElasticsearchPingsAdapter implements UMPingsAdapter { private database: DatabaseAdapter; @@ -195,7 +190,7 @@ export class ElasticsearchPingsAdapter implements UMPingsAdapter { filters?: string | null, monitorId?: string | null, statusFilter?: string | null - ): Promise { + ): Promise { const boolFilters = parseFilterQuery(filters); const additionaFilters = []; if (monitorId) { @@ -205,6 +200,8 @@ export class ElasticsearchPingsAdapter implements UMPingsAdapter { additionaFilters.push(boolFilters); } const filter = getFilterClause(dateRangeStart, dateRangeEnd, additionaFilters); + const interval = getHistogramInterval(dateRangeStart, dateRangeEnd); + const intervalFormatted = getHistogramIntervalFormatted(dateRangeStart, dateRangeEnd); const params = { index: INDEX_NAMES.HEARTBEAT, @@ -219,7 +216,7 @@ export class ElasticsearchPingsAdapter implements UMPingsAdapter { timeseries: { date_histogram: { field: '@timestamp', - fixed_interval: getHistogramInterval(dateRangeStart, dateRangeEnd), + fixed_interval: intervalFormatted, }, aggs: { down: { @@ -244,19 +241,21 @@ export class ElasticsearchPingsAdapter implements UMPingsAdapter { const result = await this.database.search(request, params); const buckets: HistogramQueryResult[] = get(result, 'aggregations.timeseries.buckets', []); - const mappedBuckets = buckets.map(bucket => { - const key: number = get(bucket, 'key'); + const histogram = buckets.map(bucket => { + const x: number = get(bucket, 'key'); const downCount: number = get(bucket, 'down.doc_count'); const upCount: number = get(bucket, 'up.doc_count'); return { - key, + x, downCount: statusFilter && statusFilter !== 'down' ? 0 : downCount, upCount: statusFilter && statusFilter !== 'up' ? 0 : upCount, y: 1, }; }); - - return formatEsBucketsForHistogram(mappedBuckets); + return { + histogram, + interval, + }; } /** diff --git a/x-pack/legacy/plugins/uptime/server/lib/helper/__test__/__snapshots__/drop_latest_buckets.test.ts.snap b/x-pack/legacy/plugins/uptime/server/lib/helper/__test__/__snapshots__/drop_latest_buckets.test.ts.snap deleted file mode 100644 index db05bb02be8a9..0000000000000 --- a/x-pack/legacy/plugins/uptime/server/lib/helper/__test__/__snapshots__/drop_latest_buckets.test.ts.snap +++ /dev/null @@ -1,9 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`dropLatestBucket drops the last of a list with greater length than 1 1`] = ` -Array [ - Object { - "prop": "val", - }, -] -`; diff --git a/x-pack/legacy/plugins/uptime/server/lib/helper/__test__/__snapshots__/format_es_buckets_for_histogram.test.ts.snap b/x-pack/legacy/plugins/uptime/server/lib/helper/__test__/__snapshots__/format_es_buckets_for_histogram.test.ts.snap deleted file mode 100644 index 1b30ee1549273..0000000000000 --- a/x-pack/legacy/plugins/uptime/server/lib/helper/__test__/__snapshots__/format_es_buckets_for_histogram.test.ts.snap +++ /dev/null @@ -1,42 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`formatEsBucketsForHistogram returns properly formatted buckets 1`] = ` -Array [ - Object { - "key": 1000, - "x": 2000, - "x0": 1000, - }, - Object { - "key": 2000, - "x": 3000, - "x0": 2000, - }, - Object { - "key": 3000, - "x": 4000, - "x0": 3000, - }, -] -`; - -exports[`formatEsBucketsForHistogram returns properly formatted object for generic call 1`] = ` -Array [ - Object { - "key": 1000, - "name": "something", - "value": 150, - "x": 2000, - "x0": 1000, - }, - Object { - "key": 2000, - "name": "something", - "value": 120, - "x": 3000, - "x0": 2000, - }, -] -`; - -exports[`formatEsBucketsForHistogram returns the provided buckets if length is below min threshold 1`] = `Array []`; diff --git a/x-pack/legacy/plugins/uptime/server/lib/helper/__test__/drop_latest_buckets.test.ts b/x-pack/legacy/plugins/uptime/server/lib/helper/__test__/drop_latest_buckets.test.ts deleted file mode 100644 index 57ae48f0c7b63..0000000000000 --- a/x-pack/legacy/plugins/uptime/server/lib/helper/__test__/drop_latest_buckets.test.ts +++ /dev/null @@ -1,25 +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 { dropLatestBucket } from '../drop_latest_bucket'; - -describe('dropLatestBucket', () => { - it('drops the last of a list with greater length than 1', () => { - const testData = [{ prop: 'val' }, { prop: 'val' }]; - const result = dropLatestBucket(testData); - expect(result).toMatchSnapshot(); - }); - it('returns an empty list when length === 1', () => { - const testData = [{ prop: 'val' }]; - const result = dropLatestBucket(testData); - expect(result).toEqual([]); - }); - it('returns an empty list when length === 0', () => { - const testData: any[] = []; - const result = dropLatestBucket(testData); - expect(result).toEqual([]); - }); -}); diff --git a/x-pack/legacy/plugins/uptime/server/lib/helper/__test__/format_es_buckets_for_histogram.test.ts b/x-pack/legacy/plugins/uptime/server/lib/helper/__test__/format_es_buckets_for_histogram.test.ts deleted file mode 100644 index 87c6aad5da032..0000000000000 --- a/x-pack/legacy/plugins/uptime/server/lib/helper/__test__/format_es_buckets_for_histogram.test.ts +++ /dev/null @@ -1,29 +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 { formatEsBucketsForHistogram } from '../format_es_buckets_for_histogram'; - -describe('formatEsBucketsForHistogram', () => { - it('returns the provided buckets if length is below min threshold', () => { - const buckets = [{ key: 1000 }]; - const result = formatEsBucketsForHistogram(buckets); - expect(result).toMatchSnapshot(); - }); - it('returns properly formatted buckets', () => { - const buckets = [{ key: 1000 }, { key: 2000 }, { key: 3000 }, { key: 4000 }]; - const result = formatEsBucketsForHistogram(buckets); - expect(result).toMatchSnapshot(); - }); - it('returns properly formatted object for generic call', () => { - const buckets = [ - { key: 1000, name: 'something', value: 150 }, - { key: 2000, name: 'something', value: 120 }, - { key: 3000, name: 'something', value: 180 }, - ]; - const result = formatEsBucketsForHistogram(buckets); - expect(result).toMatchSnapshot(); - }); -}); diff --git a/x-pack/legacy/plugins/uptime/server/lib/helper/__test__/get_histogram_interval.test.ts b/x-pack/legacy/plugins/uptime/server/lib/helper/__test__/get_histogram_interval.test.ts index 83f861d3fb467..bddca1b863ce4 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/helper/__test__/get_histogram_interval.test.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/helper/__test__/get_histogram_interval.test.ts @@ -5,29 +5,16 @@ */ import { getHistogramInterval } from '../get_histogram_interval'; +import { assertCloseTo } from '../assert_close_to'; describe('getHistogramInterval', () => { it('specifies the interval necessary to divide a given timespan into equal buckets, rounded to the nearest integer, expressed in ms', () => { - const result = getHistogramInterval('now-15m', 'now', 10); - /** - * These assertions were verbatim comparisons but that introduced - * some flakiness at the ms resolution, sometimes values like "9001ms" - * are returned. - */ - expect(result.startsWith('9000')).toBeTruthy(); - expect(result.endsWith('ms')).toBeTruthy(); - expect(result).toHaveLength(7); + const interval = getHistogramInterval('now-15m', 'now', 10); + assertCloseTo(interval, 90000, 10); }); it('will supply a default constant value for bucketCount when none is provided', () => { - const result = getHistogramInterval('now-15m', 'now'); - /** - * These assertions were verbatim comparisons but that introduced - * some flakiness at the ms resolution, sometimes values like "9001ms" - * are returned. - */ - expect(result.startsWith('3600')).toBeTruthy(); - expect(result.endsWith('ms')).toBeTruthy(); - expect(result).toHaveLength(7); + const interval = getHistogramInterval('now-15m', 'now'); + assertCloseTo(interval, 36000, 10); }); }); diff --git a/x-pack/legacy/plugins/uptime/server/lib/helper/__test__/get_histogram_interval_formatted.test.ts b/x-pack/legacy/plugins/uptime/server/lib/helper/__test__/get_histogram_interval_formatted.test.ts new file mode 100644 index 0000000000000..e67a93f24b3ca --- /dev/null +++ b/x-pack/legacy/plugins/uptime/server/lib/helper/__test__/get_histogram_interval_formatted.test.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 { getHistogramIntervalFormatted } from '../get_histogram_interval_formatted'; + +describe('getHistogramIntervalFormatted', () => { + it('specifies the interval necessary to divide a given timespan into equal buckets, rounded to the nearest integer, expressed in ms', () => { + const intervalFormatted = getHistogramIntervalFormatted('now-15m', 'now', 10); + /** + * Expected result is 90000. + * These assertions were verbatim comparisons but that introduced + * some flakiness at the ms resolution, sometimes values like "9001ms" + * are returned. + */ + expect(intervalFormatted.startsWith('9000')).toBeTruthy(); + expect(intervalFormatted.endsWith('ms')).toBeTruthy(); + expect(intervalFormatted).toHaveLength(7); + }); + + it('will supply a default constant value for bucketCount when none is provided', () => { + const intervalFormatted = getHistogramIntervalFormatted('now-15m', 'now'); + /** + * Expected result is 36000. + * These assertions were verbatim comparisons but that introduced + * some flakiness at the ms resolution, sometimes values like "9001ms" + * are returned. + */ + expect(intervalFormatted.startsWith('3600')).toBeTruthy(); + expect(intervalFormatted.endsWith('ms')).toBeTruthy(); + expect(intervalFormatted).toHaveLength(7); + }); +}); diff --git a/x-pack/legacy/plugins/uptime/server/lib/helper/drop_latest_bucket.ts b/x-pack/legacy/plugins/uptime/server/lib/helper/drop_latest_bucket.ts deleted file mode 100644 index 4d072266fa4ea..0000000000000 --- a/x-pack/legacy/plugins/uptime/server/lib/helper/drop_latest_bucket.ts +++ /dev/null @@ -1,14 +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. - */ - -/** - * We've had numerous requests to not display semi-full buckets (i.e. it is 13:01 and the - * bounds of our bucket are 13:00-13:05). If the first bucket isn't done filling, we'll - * start out with nothing returned, otherwise we drop the most recent bucket. - * @param buckets The bucket list - */ -export const dropLatestBucket = (buckets: any[]) => - buckets.length > 1 ? buckets.slice(0, buckets.length - 1) : []; diff --git a/x-pack/legacy/plugins/uptime/server/lib/helper/format_es_buckets_for_histogram.ts b/x-pack/legacy/plugins/uptime/server/lib/helper/format_es_buckets_for_histogram.ts deleted file mode 100644 index 1b0a2bfdfc5c0..0000000000000 --- a/x-pack/legacy/plugins/uptime/server/lib/helper/format_es_buckets_for_histogram.ts +++ /dev/null @@ -1,42 +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 { UMESBucket, UMESHistogramBucket } from '../adapters/database'; -import { dropLatestBucket } from './drop_latest_bucket'; - -/** - * The charting library we're currently using requires histogram data points have an - * x and an x0 property, where x0 is the beginning of a data point and x provides - * the size of the point from the start. This function attempts to generalize the - * concept so any bucket that has a numeric value as its key can be put into this format. - * - * Additionally, histograms that stack horizontally instead of vertically need to have - * a y and a y0 value. We're not doing this currently but with some minor modification - * this function could provide formatting for those buckets as well. - * @param buckets The ES data to format. - */ -export function formatEsBucketsForHistogram( - buckets: T[] -): Array { - // wait for first bucket to fill up - if (buckets.length < 2) { - return []; - } - const TERMINAL_INDEX = buckets.length - 1; - const { key: terminalBucketTime } = buckets[TERMINAL_INDEX]; - // drop the most recent bucket to avoid returning incomplete bucket - return dropLatestBucket(buckets).map((item, index, array) => { - const { key } = item; - const nextItem = array[index + 1]; - const bucketSize = nextItem ? Math.abs(nextItem.key - key) : Math.abs(terminalBucketTime - key); - - return { - x: key + bucketSize, - x0: key, - ...item, - }; - }); -} diff --git a/x-pack/legacy/plugins/uptime/server/lib/helper/get_histogram_interval.ts b/x-pack/legacy/plugins/uptime/server/lib/helper/get_histogram_interval.ts index 107a635366a0b..0dedc3e456f51 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/helper/get_histogram_interval.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/helper/get_histogram_interval.ts @@ -11,7 +11,7 @@ export const getHistogramInterval = ( dateRangeStart: string, dateRangeEnd: string, bucketCount?: number -): string => { +): number => { const from = DateMath.parse(dateRangeStart); const to = DateMath.parse(dateRangeEnd); if (from === undefined) { @@ -20,7 +20,5 @@ export const getHistogramInterval = ( if (to === undefined) { throw Error('Invalid dateRangeEnd value'); } - return `${Math.round( - (to.valueOf() - from.valueOf()) / (bucketCount || QUERY.DEFAULT_BUCKET_COUNT) - )}ms`; + return Math.round((to.valueOf() - from.valueOf()) / (bucketCount || QUERY.DEFAULT_BUCKET_COUNT)); }; diff --git a/x-pack/legacy/plugins/uptime/server/lib/helper/get_histogram_interval_formatted.ts b/x-pack/legacy/plugins/uptime/server/lib/helper/get_histogram_interval_formatted.ts new file mode 100644 index 0000000000000..29af862611ca4 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/server/lib/helper/get_histogram_interval_formatted.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 { getHistogramInterval } from './get_histogram_interval'; + +export const getHistogramIntervalFormatted = ( + dateRangeStart: string, + dateRangeEnd: string, + bucketCount?: number +): string => `${getHistogramInterval(dateRangeStart, dateRangeEnd, bucketCount)}ms`; diff --git a/x-pack/legacy/plugins/uptime/server/lib/helper/index.ts b/x-pack/legacy/plugins/uptime/server/lib/helper/index.ts index a2a72825c6b98..4c88da7eca85a 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/helper/index.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/helper/index.ts @@ -4,9 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -export { dropLatestBucket } from './drop_latest_bucket'; -export { formatEsBucketsForHistogram } from './format_es_buckets_for_histogram'; export { getFilterClause } from './get_filter_clause'; export { getHistogramInterval } from './get_histogram_interval'; +export { getHistogramIntervalFormatted } from './get_histogram_interval_formatted'; export { parseFilterQuery } from './parse_filter_query'; export { assertCloseTo } from './assert_close_to'; diff --git a/x-pack/legacy/plugins/watcher/common/types/action_types.ts b/x-pack/legacy/plugins/watcher/common/types/action_types.ts index 8247df3293d17..123bf0f58db9d 100644 --- a/x-pack/legacy/plugins/watcher/common/types/action_types.ts +++ b/x-pack/legacy/plugins/watcher/common/types/action_types.ts @@ -34,7 +34,7 @@ export interface EmailAction extends BaseAction { export interface LoggingAction extends BaseAction { type: LoggingActionType; - iconClass: 'loggingApp'; + iconClass: 'logsApp'; text: string; } diff --git a/x-pack/legacy/plugins/watcher/public/models/action/logging_action.js b/x-pack/legacy/plugins/watcher/public/models/action/logging_action.js index e11ebedc874c0..477ee9e2230b4 100644 --- a/x-pack/legacy/plugins/watcher/public/models/action/logging_action.js +++ b/x-pack/legacy/plugins/watcher/public/models/action/logging_action.js @@ -68,7 +68,7 @@ export class LoggingAction extends BaseAction { static typeName = i18n.translate('xpack.watcher.models.loggingAction.typeName', { defaultMessage: 'Logging', }); - static iconClass = 'loggingApp'; + static iconClass = 'logsApp'; static selectMessage = i18n.translate('xpack.watcher.models.loggingAction.selectMessageText', { defaultMessage: 'Add an item to the logs.', }); diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit_simulate.tsx b/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit_simulate.tsx index 5d16ac3aa18f0..e57a875aa4356 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit_simulate.tsx +++ b/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/json_watch_edit/json_watch_edit_simulate.tsx @@ -158,12 +158,12 @@ export const JsonWatchEditSimulate = ({ +

{i18n.translate( 'xpack.watcher.sections.watchEdit.simulate.form.triggerOverridesTitle', { defaultMessage: 'Trigger' } )} -

+ } description={i18n.translate( 'xpack.watcher.sections.watchEdit.simulate.form.triggerOverridesDescription', @@ -247,12 +247,12 @@ export const JsonWatchEditSimulate = ({ +

{i18n.translate( 'xpack.watcher.sections.watchEdit.simulate.form.conditionOverridesTitle', { defaultMessage: 'Condition' } )} -

+ } description={i18n.translate( 'xpack.watcher.sections.watchEdit.simulate.form.conditionOverridesDescription', @@ -283,12 +283,12 @@ export const JsonWatchEditSimulate = ({ fullWidth idAria="simulateExecutionActionModesDescription" title={ -

+

{i18n.translate( 'xpack.watcher.sections.watchEdit.simulate.form.actionOverridesTitle', { defaultMessage: 'Actions' } )} -

+ } description={ +

{i18n.translate( 'xpack.watcher.sections.watchEdit.simulate.form.inputOverridesTitle', { defaultMessage: 'Input' } )} -

+ } description={i18n.translate( 'xpack.watcher.sections.watchEdit.simulate.form.inputOverridesDescription', diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_panel.tsx b/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_panel.tsx index 504c5e10ed665..a2e46652429ea 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_panel.tsx +++ b/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_action_panel.tsx @@ -29,7 +29,7 @@ export const WatchActionsPanel: React.FunctionComponent = ({ actionErrors -

+

{i18n.translate('xpack.watcher.sections.watchEdit.actions.title', { defaultMessage: 'Perform {watchActionsCount, plural, one{# action} other {# actions}} when condition is met', @@ -37,7 +37,7 @@ export const WatchActionsPanel: React.FunctionComponent = ({ actionErrors watchActionsCount: watch.actions.length, }, })} -

+
diff --git a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_edit.tsx b/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_edit.tsx index c479cad1bc825..ee89d9bfc176a 100644 --- a/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_edit.tsx +++ b/x-pack/legacy/plugins/watcher/public/sections/watch_edit/components/threshold_watch_edit/threshold_watch_edit.tsx @@ -441,12 +441,12 @@ export const ThresholdWatchEdit = ({ pageTitle }: { pageTitle: string }) => { {shouldShowThresholdExpression ? ( -

+

-

+
diff --git a/x-pack/package.json b/x-pack/package.json index d55910ccd5a5c..6a428e01fa16f 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -98,7 +98,6 @@ "@types/recompose": "^0.30.6", "@types/reduce-reducers": "^0.3.0", "@types/redux-actions": "^2.2.1", - "@types/rimraf": "^2.0.2", "@types/sinon": "^7.0.13", "@types/storybook__addon-actions": "^3.4.3", "@types/storybook__addon-info": "^4.1.2", @@ -112,28 +111,22 @@ "@types/xml-crypto": "^1.4.0", "@types/xml2js": "^0.4.5", "abab": "^1.0.4", - "ansicolors": "0.3.2", "axios": "^0.19.0", "babel-jest": "^24.9.0", - "babel-plugin-inline-react-svg": "^1.1.0", - "babel-plugin-mock-imports": "^1.0.1", "babel-plugin-require-context-hook": "npm:babel-plugin-require-context-hook-babel7@1.0.0", - "babel-plugin-transform-react-remove-prop-types": "^0.4.24", "base64-js": "^1.3.1", "base64url": "^3.0.1", "chalk": "^2.4.2", "chance": "1.0.18", - "checksum": "0.1.1", "cheerio": "0.22.0", "commander": "3.0.0", "copy-webpack-plugin": "^5.0.4", "cypress": "^3.4.1", - "del": "^4.1.1", "enzyme": "^3.10.0", "enzyme-adapter-react-16": "^1.14.0", "enzyme-adapter-utils": "^1.12.0", "enzyme-to-json": "^3.3.4", - "execa": "^1.0.0", + "execa": "^3.2.0", "fancy-log": "^1.3.2", "fetch-mock": "^7.3.9", "graphql-code-generator": "^0.18.2", @@ -145,7 +138,6 @@ "graphql-codegen-typescript-server": "^0.18.2", "gulp": "4.0.2", "gulp-mocha": "^7.0.2", - "gulp-multi-process": "1.3.1", "hapi": "^17.5.3", "jest": "^24.9.0", "jest-cli": "^24.9.0", @@ -157,7 +149,6 @@ "mocha-multi-reporters": "^1.1.7", "mochawesome": "^4.1.0", "mochawesome-merge": "^2.0.1", - "mochawesome-report-generator": "^4.0.1", "mustache": "^2.3.0", "mutation-observer": "^1.0.3", "node-fetch": "^2.6.0", @@ -170,8 +161,6 @@ "react-hooks-testing-library": "^0.3.8", "react-test-renderer": "^16.8.0", "react-testing-library": "^6.0.0", - "redux-test-utils": "0.2.2", - "rsync": "0.6.1", "sass-loader": "^7.3.1", "sass-resources-loader": "^2.0.1", "simple-git": "1.116.0", @@ -184,6 +173,7 @@ "ts-loader": "^6.0.4", "typescript": "3.5.3", "vinyl-fs": "^3.0.3", + "whatwg-fetch": "^3.0.0", "xml-crypto": "^1.4.0", "yargs": "4.8.1" }, @@ -194,7 +184,7 @@ "@elastic/ctags-langserver": "^0.1.11", "@elastic/datemath": "5.0.2", "@elastic/ems-client": "1.0.5", - "@elastic/eui": "14.5.0", + "@elastic/eui": "14.7.0", "@elastic/filesaver": "1.1.2", "@elastic/javascript-typescript-langserver": "^0.3.3", "@elastic/lsp-extension": "^0.1.2", @@ -210,9 +200,7 @@ "@kbn/interpreter": "1.0.0", "@kbn/ui-framework": "1.0.0", "@mapbox/mapbox-gl-draw": "^1.1.1", - "@samverschueren/stream-to-observable": "^0.3.0", "@scant/router": "^0.1.0", - "@slack/client": "^4.8.0", "@slack/webhook": "^5.0.0", "@turf/boolean-contains": "6.0.1", "angular-resource": "1.7.8", @@ -243,8 +231,8 @@ "cytoscape-dagre": "^2.2.2", "d3": "3.5.17", "d3-scale": "1.0.7", - "dataloader": "^1.4.0", "dedent": "^0.7.0", + "del": "^5.1.0", "dragselect": "1.13.1", "elasticsearch": "^16.4.0", "extract-zip": "1.6.7", @@ -267,7 +255,6 @@ "handlebars": "4.3.5", "history": "4.9.0", "history-extra": "^5.0.1", - "humps": "2.0.1", "i18n-iso-countries": "^4.3.1", "icalendar": "0.7.1", "idx": "^2.5.6", @@ -284,11 +271,7 @@ "jsts": "^1.6.2", "lodash": "npm:@elastic/lodash@3.10.1-kibana3", "lodash.keyby": "^4.6.0", - "lodash.lowercase": "^4.3.0", "lodash.mean": "^4.1.0", - "lodash.omitby": "^4.6.0", - "lodash.orderby": "4.6.0", - "lodash.pickby": "^4.6.0", "lodash.topath": "^4.5.2", "lodash.uniqby": "^4.7.0", "lz-string": "^1.4.4", @@ -297,9 +280,9 @@ "markdown-it": "^8.4.1", "memoize-one": "^5.0.0", "mime": "^2.4.4", - "moment": "^2.20.1", - "moment-duration-format": "^1.3.0", - "moment-timezone": "^0.5.14", + "moment": "^2.24.0", + "moment-duration-format": "^2.3.2", + "moment-timezone": "^0.5.27", "monaco-editor": "~0.17.0", "ngreact": "^0.5.1", "nock": "10.0.6", @@ -314,7 +297,6 @@ "pluralize": "3.1.0", "pngjs": "3.4.0", "polished": "^1.9.2", - "popper.js": "^1.14.3", "postcss-prefix-selector": "^1.7.2", "prop-types": "^15.6.0", "proper-lockfile": "^3.2.0", @@ -324,21 +306,18 @@ "react": "^16.8.0", "react-apollo": "^2.1.4", "react-beautiful-dnd": "^8.0.7", - "react-clipboard.js": "^1.1.2", "react-datetime": "^2.14.0", "react-dom": "^16.8.0", "react-dropzone": "^4.2.9", "react-fast-compare": "^2.0.4", "react-markdown": "^3.4.1", - "react-moment-proptypes": "^1.6.0", + "react-moment-proptypes": "^1.7.0", "react-monaco-editor": "~0.27.0", "react-portal": "^3.2.0", "react-redux": "^5.1.1", - "react-redux-request": "^1.5.6", "react-resize-detector": "^4.2.0", "react-reverse-portal": "^1.0.4", "react-router-dom": "^4.3.1", - "react-select": "^1.2.1", "react-shortcuts": "^2.0.0", "react-sticky": "^6.0.3", "react-syntax-highlighter": "^5.7.0", @@ -355,7 +334,6 @@ "request": "^2.88.0", "reselect": "3.0.1", "resize-observer-polyfill": "^1.5.0", - "rimraf": "^2.7.1", "rison-node": "0.3.1", "rxjs": "^6.2.1", "semver": "5.7.0", diff --git a/x-pack/plugins/code/kibana.json b/x-pack/plugins/code/kibana.json index c591604ec9720..815bc147d3cfe 100644 --- a/x-pack/plugins/code/kibana.json +++ b/x-pack/plugins/code/kibana.json @@ -4,6 +4,5 @@ "kibanaVersion": "kibana", "configPath": ["xpack", "code"], "server": true, - "ui": false, - "requiredPlugins": ["features"] + "ui": false } diff --git a/x-pack/plugins/code/server/config.ts b/x-pack/plugins/code/server/config.ts index b06ff2c94cb18..4911537aa0c98 100644 --- a/x-pack/plugins/code/server/config.ts +++ b/x-pack/plugins/code/server/config.ts @@ -5,117 +5,9 @@ */ import { schema } from '@kbn/config-schema'; -import moment from 'moment'; - -// TODO: update these legacy imports to new ones. -import { - LanguageServers, - LanguageServersDeveloping, -} from '../../../legacy/plugins/code/server/lsp/language_servers'; -import { DEFAULT_WATERMARK_LOW_PERCENTAGE } from '../../../legacy/plugins/code/server/disk_watermark'; const createCodeConfigSchema = () => { - const langSwitches: any = {}; - LanguageServers.forEach(lang => { - langSwitches[lang.name] = schema.object({ - enabled: schema.boolean({ defaultValue: true }), - }); - }); - LanguageServersDeveloping.forEach(lang => { - langSwitches[lang.name] = schema.object({ - enabled: schema.boolean({ defaultValue: false }), - }); - }); - - return schema.object({ - ui: schema.object({ - enabled: schema.boolean({ defaultValue: true }), - }), - enabled: schema.boolean({ defaultValue: true }), - diffPage: schema.object({ - enabled: schema.boolean({ defaultValue: false }), - }), - integrations: schema.object({ - enabled: schema.boolean({ defaultValue: false }), - }), - queueIndex: schema.string({ defaultValue: '.code_internal-worker-queue' }), - // 1 hour by default. - queueTimeoutMs: schema.number({ - defaultValue: moment.duration(1, 'hour').asMilliseconds(), - }), - // The frequency which update scheduler executes. 1 minute by default. - updateFrequencyMs: schema.number({ - defaultValue: moment.duration(1, 'minute').asMilliseconds(), - }), - // The frequency which index scheduler executes. 1 day by default. - indexFrequencyMs: schema.number({ - defaultValue: moment.duration(1, 'day').asMilliseconds(), - }), - // The frequency which each repo tries to update. 5 minutes by default. - updateRepoFrequencyMs: schema.number({ - defaultValue: moment.duration(5, 'minute').asMilliseconds(), - }), - // The frequency which each repo tries to index. 1 day by default. - indexRepoFrequencyMs: schema.number({ - defaultValue: moment.duration(1, 'day').asMilliseconds(), - }), - // whether we want to show more logs - verbose: schema.boolean({ defaultValue: false }), - lsp: schema.object({ - ...langSwitches, - // timeout of a request - requestTimeoutMs: schema.number({ - defaultValue: moment.duration(10, 'second').asMilliseconds(), - }), - // if we want the language server run in seperately - detach: schema.boolean({ defaultValue: false }), - // enable oom_score_adj on linux - oomScoreAdj: schema.boolean({ defaultValue: true }), - }), - repos: schema.arrayOf(schema.string(), { defaultValue: [] }), - security: schema.object({ - enableMavenImport: schema.boolean({ defaultValue: true }), - enableGradleImport: schema.boolean({ defaultValue: false }), - installGoDependency: schema.boolean({ defaultValue: false }), - installNodeDependency: schema.boolean({ defaultValue: true }), - gitHostWhitelist: schema.arrayOf(schema.string(), { - defaultValue: [ - 'github.com', - 'gitlab.com', - 'bitbucket.org', - 'gitbox.apache.org', - 'eclipse.org', - ], - }), - gitProtocolWhitelist: schema.arrayOf(schema.string(), { - defaultValue: ['https', 'git', 'ssh'], - }), - enableJavaSecurityManager: schema.boolean({ defaultValue: true }), - extraJavaRepositoryWhitelist: schema.arrayOf(schema.string(), { - defaultValue: [], - }), - }), - disk: schema.object({ - thresholdEnabled: schema.boolean({ defaultValue: true }), - watermarkLow: schema.string({ defaultValue: `${DEFAULT_WATERMARK_LOW_PERCENTAGE}%` }), - }), - maxWorkspace: schema.number({ defaultValue: 5 }), // max workspace folder for each language server - enableGlobalReference: schema.boolean({ defaultValue: false }), // Global reference as optional feature for now - enableCommitIndexing: schema.boolean({ defaultValue: false }), - codeNodeUrl: schema.maybe(schema.string()), - clustering: schema.object({ - enabled: schema.boolean({ defaultValue: false }), - codeNodes: schema.arrayOf( - schema.object({ - id: schema.string(), - address: schema.string(), - }), - { - defaultValue: [], - } - ), - }), - }); + return schema.any({ defaultValue: {} }); }; export const CodeConfigSchema = createCodeConfigSchema(); diff --git a/x-pack/plugins/code/server/index.ts b/x-pack/plugins/code/server/index.ts index 554974349f2fe..954ae629e028c 100644 --- a/x-pack/plugins/code/server/index.ts +++ b/x-pack/plugins/code/server/index.ts @@ -8,8 +8,6 @@ import { PluginInitializerContext } from 'src/core/server'; import { CodeConfigSchema } from './config'; import { CodePlugin } from './plugin'; -export { PluginSetupContract } from './plugin'; - export const config = { schema: CodeConfigSchema }; export const plugin = (initializerContext: PluginInitializerContext) => new CodePlugin(initializerContext); diff --git a/x-pack/plugins/code/server/plugin.test.ts b/x-pack/plugins/code/server/plugin.test.ts new file mode 100644 index 0000000000000..23a16ef8f3b82 --- /dev/null +++ b/x-pack/plugins/code/server/plugin.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 { coreMock } from '../../../../src/core/server/mocks'; + +import { CodePlugin } from './plugin'; + +describe('Code Plugin', () => { + describe('setup()', () => { + it('does not log deprecation message if no xpack.code.* configurations are set', async () => { + const context = coreMock.createPluginInitializerContext(); + const plugin = new CodePlugin(context); + + await plugin.setup(); + + expect(context.logger.get).not.toHaveBeenCalled(); + }); + + it('logs deprecation message if any xpack.code.* configurations are set', async () => { + const context = coreMock.createPluginInitializerContext({ + foo: 'bar', + }); + const warn = jest.fn(); + context.logger.get = jest.fn().mockReturnValue({ warn }); + const plugin = new CodePlugin(context); + + await plugin.setup(); + + expect(context.logger.get).toHaveBeenCalledWith('config', 'deprecation'); + expect(warn.mock.calls[0][0]).toMatchInlineSnapshot( + `"The experimental app \\"Code\\" has been removed from Kibana. Remove all xpack.code.* configurations from kibana.yml so Kibana does not fail to start up in the next major version."` + ); + }); + }); +}); diff --git a/x-pack/plugins/code/server/plugin.ts b/x-pack/plugins/code/server/plugin.ts index 40f4bc8d4749e..8acff96eef3aa 100644 --- a/x-pack/plugins/code/server/plugin.ts +++ b/x-pack/plugins/code/server/plugin.ts @@ -4,35 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { i18n } from '@kbn/i18n'; import { first } from 'rxjs/operators'; import { TypeOf } from '@kbn/config-schema'; -import { - CoreSetup, - IClusterClient, - LoggerFactory, - PluginInitializerContext, - RecursiveReadonly, -} from 'src/core/server'; -// import { deepFreeze } from '../../../../src/core/utils'; -import { PluginSetupContract as FeaturesSetupContract } from '../../features/server'; +import { PluginInitializerContext } from 'src/core/server'; import { CodeConfigSchema } from './config'; -import { SAVED_OBJ_REPO } from '../../../legacy/plugins/code/common/constants'; - -/** - * Describes public Code plugin contract returned at the `setup` stage. - */ -export interface PluginSetupContract { - /** @deprecated */ - legacy: { - config: TypeOf; - logger: LoggerFactory; - http: any; - elasticsearch: { - adminClient$: IClusterClient; - }; - }; -} /** * Represents Code Plugin instance that will be managed by the Kibana plugin system. @@ -40,63 +15,23 @@ export interface PluginSetupContract { export class CodePlugin { constructor(private readonly initializerContext: PluginInitializerContext) {} - public async setup( - coreSetup: CoreSetup, - { features }: { features: FeaturesSetupContract } - ): Promise> { + public async setup() { const config = await this.initializerContext.config .create>() .pipe(first()) .toPromise(); - features.registerFeature({ - id: 'code', - name: i18n.translate('xpack.code.featureRegistry.codeFeatureName', { - defaultMessage: 'Code', - }), - icon: 'codeApp', - navLinkId: 'code', - app: ['code', 'kibana'], - catalogue: [], // TODO add catalogue here - privileges: { - all: { - excludeFromBasePrivileges: true, - api: ['code_user', 'code_admin'], - savedObject: { - all: [SAVED_OBJ_REPO], - read: ['config'], - }, - ui: ['show', 'user', 'admin'], - }, - read: { - api: ['code_user'], - savedObject: { - all: [], - read: ['config', SAVED_OBJ_REPO], - }, - ui: ['show', 'user'], - }, - }, - }); - - return { - /** @deprecated */ - legacy: { - config, - logger: this.initializerContext.logger, - http: coreSetup.http, - elasticsearch: { - adminClient$: await coreSetup.elasticsearch.adminClient$.pipe(first()).toPromise(), - }, - }, - }; + if (config && Object.keys(config).length > 0) { + this.initializerContext.logger + .get('config', 'deprecation') + .warn( + 'The experimental app "Code" has been removed from Kibana. Remove all xpack.code.* ' + + 'configurations from kibana.yml so Kibana does not fail to start up in the next major version.' + ); + } } - public start() { - this.initializerContext.logger.get().debug('Starting Code plugin'); - } + public start() {} - public stop() { - this.initializerContext.logger.get().debug('Stopping Code plugin'); - } + public stop() {} } diff --git a/x-pack/plugins/security/kibana.json b/x-pack/plugins/security/kibana.json index 7ac9d654eb07e..9f243a7dfb2fc 100644 --- a/x-pack/plugins/security/kibana.json +++ b/x-pack/plugins/security/kibana.json @@ -4,5 +4,5 @@ "kibanaVersion": "kibana", "configPath": ["xpack", "security"], "server": true, - "ui": false + "ui": true } diff --git a/x-pack/legacy/plugins/code/webpackShims/simple-git.d.ts b/x-pack/plugins/security/public/index.ts similarity index 53% rename from x-pack/legacy/plugins/code/webpackShims/simple-git.d.ts rename to x-pack/plugins/security/public/index.ts index 708eff2abed28..12a3092039d0d 100644 --- a/x-pack/legacy/plugins/code/webpackShims/simple-git.d.ts +++ b/x-pack/plugins/security/public/index.ts @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -declare module '@elastic/simple-git/dist/util/paths' { - // eslint-disable-next-line - export default function paths(platform: any): any; -} +import { PluginInitializer } from 'src/core/public'; +import { SecurityPlugin, SecurityPluginSetup, SecurityPluginStart } from './plugin'; + +export const plugin: PluginInitializer = () => + new SecurityPlugin(); diff --git a/x-pack/plugins/security/public/plugin.ts b/x-pack/plugins/security/public/plugin.ts new file mode 100644 index 0000000000000..55d125bf993ec --- /dev/null +++ b/x-pack/plugins/security/public/plugin.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 { Plugin, CoreSetup } from 'src/core/public'; +import { + SessionExpired, + SessionTimeout, + SessionTimeoutHttpInterceptor, + UnauthorizedResponseHttpInterceptor, +} from './session'; + +export class SecurityPlugin implements Plugin { + public setup(core: CoreSetup) { + const { http, notifications, injectedMetadata } = core; + const { basePath, anonymousPaths } = http; + anonymousPaths.register('/login'); + anonymousPaths.register('/logout'); + anonymousPaths.register('/logged_out'); + + const sessionExpired = new SessionExpired(basePath); + http.intercept(new UnauthorizedResponseHttpInterceptor(sessionExpired, anonymousPaths)); + const sessionTimeout = new SessionTimeout( + injectedMetadata.getInjectedVar('sessionTimeout', null) as number | null, + notifications, + sessionExpired, + http + ); + http.intercept(new SessionTimeoutHttpInterceptor(sessionTimeout, anonymousPaths)); + + return { + anonymousPaths, + sessionTimeout, + }; + } + + public start() {} +} + +export type SecurityPluginSetup = ReturnType; +export type SecurityPluginStart = ReturnType; diff --git a/x-pack/plugins/security/public/session/index.ts b/x-pack/plugins/security/public/session/index.ts new file mode 100644 index 0000000000000..253207dc1b717 --- /dev/null +++ b/x-pack/plugins/security/public/session/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 { SessionExpired } from './session_expired'; +export { SessionTimeout } from './session_timeout'; +export { SessionTimeoutHttpInterceptor } from './session_timeout_http_interceptor'; +export { UnauthorizedResponseHttpInterceptor } from './unauthorized_response_http_interceptor'; diff --git a/x-pack/legacy/plugins/code/server/utils/log_factory.ts b/x-pack/plugins/security/public/session/session_expired.mock.ts similarity index 59% rename from x-pack/legacy/plugins/code/server/utils/log_factory.ts rename to x-pack/plugins/security/public/session/session_expired.mock.ts index c43e0c2d18ed1..e894caafd9594 100644 --- a/x-pack/legacy/plugins/code/server/utils/log_factory.ts +++ b/x-pack/plugins/security/public/session/session_expired.mock.ts @@ -4,8 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Logger } from '../log'; +import { ISessionExpired } from './session_expired'; -export interface LoggerFactory { - getLogger(tags: string[]): Logger; +export function createSessionExpiredMock() { + return { + logout: jest.fn(), + } as jest.Mocked; } diff --git a/x-pack/plugins/security/public/session/session_expired.test.ts b/x-pack/plugins/security/public/session/session_expired.test.ts new file mode 100644 index 0000000000000..9c0e4cd8036cc --- /dev/null +++ b/x-pack/plugins/security/public/session/session_expired.test.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 { coreMock } from 'src/core/public/mocks'; +import { SessionExpired } from './session_expired'; + +const mockCurrentUrl = (url: string) => window.history.pushState({}, '', url); + +it('redirects user to "/logout" when there is no basePath', async () => { + const { basePath } = coreMock.createSetup().http; + mockCurrentUrl('/foo/bar?baz=quz#quuz'); + const sessionExpired = new SessionExpired(basePath); + const newUrlPromise = new Promise(resolve => { + jest.spyOn(window.location, 'assign').mockImplementation(url => { + resolve(url); + }); + }); + + sessionExpired.logout(); + + const url = await newUrlPromise; + expect(url).toBe( + `/logout?next=${encodeURIComponent('/foo/bar?baz=quz#quuz')}&msg=SESSION_EXPIRED` + ); +}); + +it('redirects user to "/${basePath}/logout" and removes basePath from next parameter when there is a basePath', async () => { + const { basePath } = coreMock.createSetup({ basePath: '/foo' }).http; + mockCurrentUrl('/foo/bar?baz=quz#quuz'); + const sessionExpired = new SessionExpired(basePath); + const newUrlPromise = new Promise(resolve => { + jest.spyOn(window.location, 'assign').mockImplementation(url => { + resolve(url); + }); + }); + + sessionExpired.logout(); + + const url = await newUrlPromise; + expect(url).toBe( + `/foo/logout?next=${encodeURIComponent('/bar?baz=quz#quuz')}&msg=SESSION_EXPIRED` + ); +}); diff --git a/x-pack/plugins/security/public/session/session_expired.ts b/x-pack/plugins/security/public/session/session_expired.ts new file mode 100644 index 0000000000000..3ef15088bb288 --- /dev/null +++ b/x-pack/plugins/security/public/session/session_expired.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 { HttpSetup } from 'src/core/public'; + +export interface ISessionExpired { + logout(): void; +} + +export class SessionExpired { + constructor(private basePath: HttpSetup['basePath']) {} + + logout() { + const next = this.basePath.remove( + `${window.location.pathname}${window.location.search}${window.location.hash}` + ); + window.location.assign( + this.basePath.prepend(`/logout?next=${encodeURIComponent(next)}&msg=SESSION_EXPIRED`) + ); + } +} diff --git a/x-pack/legacy/plugins/code/server/lsp/process/controlled_program.ts b/x-pack/plugins/security/public/session/session_timeout.mock.ts similarity index 59% rename from x-pack/legacy/plugins/code/server/lsp/process/controlled_program.ts rename to x-pack/plugins/security/public/session/session_timeout.mock.ts index 228d9ae9dcc2d..9917a50279083 100644 --- a/x-pack/legacy/plugins/code/server/lsp/process/controlled_program.ts +++ b/x-pack/plugins/security/public/session/session_timeout.mock.ts @@ -4,12 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -export interface ControlledProgram { - readonly pid: number; +import { ISessionTimeout } from './session_timeout'; - kill(force: boolean): void; - - onExit(callback: () => void): void; - - killed(): boolean; +export function createSessionTimeoutMock() { + return { + extend: jest.fn(), + } as jest.Mocked; } diff --git a/x-pack/plugins/security/public/session/session_timeout.test.tsx b/x-pack/plugins/security/public/session/session_timeout.test.tsx new file mode 100644 index 0000000000000..776247dda94e6 --- /dev/null +++ b/x-pack/plugins/security/public/session/session_timeout.test.tsx @@ -0,0 +1,171 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { coreMock } from 'src/core/public/mocks'; +import { SessionTimeout } from './session_timeout'; +import { createSessionExpiredMock } from './session_expired.mock'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; + +jest.useFakeTimers(); + +const expectNoWarningToast = ( + notifications: ReturnType['notifications'] +) => { + expect(notifications.toasts.add).not.toHaveBeenCalled(); +}; + +const expectWarningToast = ( + notifications: ReturnType['notifications'], + toastLifeTimeMS: number = 60000 +) => { + expect(notifications.toasts.add).toHaveBeenCalledTimes(1); + expect(notifications.toasts.add.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "color": "warning", + "text": , + "title": "Warning", + "toastLifeTimeMs": ${toastLifeTimeMS}, + }, + ] + `); +}; + +const expectWarningToastHidden = ( + notifications: ReturnType['notifications'], + toast: symbol +) => { + expect(notifications.toasts.remove).toHaveBeenCalledTimes(1); + expect(notifications.toasts.remove).toHaveBeenCalledWith(toast); +}; + +describe('warning toast', () => { + test(`shows session expiration warning toast`, () => { + const { notifications, http } = coreMock.createSetup(); + const sessionExpired = createSessionExpiredMock(); + const sessionTimeout = new SessionTimeout(2 * 60 * 1000, notifications, sessionExpired, http); + + sessionTimeout.extend(); + // we display the warning a minute before we expire the the session, which is 5 seconds before it actually expires + jest.advanceTimersByTime(55 * 1000); + expectWarningToast(notifications); + }); + + test(`extend delays the warning toast`, () => { + const { notifications, http } = coreMock.createSetup(); + const sessionExpired = createSessionExpiredMock(); + const sessionTimeout = new SessionTimeout(2 * 60 * 1000, notifications, sessionExpired, http); + + sessionTimeout.extend(); + jest.advanceTimersByTime(54 * 1000); + expectNoWarningToast(notifications); + + sessionTimeout.extend(); + jest.advanceTimersByTime(54 * 1000); + expectNoWarningToast(notifications); + + jest.advanceTimersByTime(1 * 1000); + + expectWarningToast(notifications); + }); + + test(`extend hides displayed warning toast`, () => { + const { notifications, http } = coreMock.createSetup(); + const toast = Symbol(); + notifications.toasts.add.mockReturnValue(toast as any); + const sessionExpired = createSessionExpiredMock(); + const sessionTimeout = new SessionTimeout(2 * 60 * 1000, notifications, sessionExpired, http); + + sessionTimeout.extend(); + // we display the warning a minute before we expire the the session, which is 5 seconds before it actually expires + jest.advanceTimersByTime(55 * 1000); + expectWarningToast(notifications); + + sessionTimeout.extend(); + expectWarningToastHidden(notifications, toast); + }); + + test('clicking "extend" causes a new HTTP request (which implicitly extends the session)', () => { + const { notifications, http } = coreMock.createSetup(); + const sessionExpired = createSessionExpiredMock(); + const sessionTimeout = new SessionTimeout(2 * 60 * 1000, notifications, sessionExpired, http); + + sessionTimeout.extend(); + // we display the warning a minute before we expire the the session, which is 5 seconds before it actually expires + jest.advanceTimersByTime(55 * 1000); + expectWarningToast(notifications); + + expect(http.get).not.toHaveBeenCalled(); + const toastInput = notifications.toasts.add.mock.calls[0][0]; + expect(toastInput).toHaveProperty('text'); + const reactComponent = (toastInput as any).text; + const wrapper = mountWithIntl(reactComponent); + wrapper.find('EuiButton[data-test-subj="refreshSessionButton"]').simulate('click'); + expect(http.get).toHaveBeenCalled(); + }); + + test('when the session timeout is shorter than 65 seconds, display the warning immediately and for a shorter duration', () => { + const { notifications, http } = coreMock.createSetup(); + const sessionExpired = createSessionExpiredMock(); + const sessionTimeout = new SessionTimeout(64 * 1000, notifications, sessionExpired, http); + + sessionTimeout.extend(); + jest.advanceTimersByTime(0); + expectWarningToast(notifications, 59 * 1000); + }); +}); + +describe('session expiration', () => { + test(`expires the session 5 seconds before it really expires`, () => { + const { notifications, http } = coreMock.createSetup(); + const sessionExpired = createSessionExpiredMock(); + const sessionTimeout = new SessionTimeout(2 * 60 * 1000, notifications, sessionExpired, http); + + sessionTimeout.extend(); + jest.advanceTimersByTime(114 * 1000); + expect(sessionExpired.logout).not.toHaveBeenCalled(); + + jest.advanceTimersByTime(1 * 1000); + expect(sessionExpired.logout).toHaveBeenCalled(); + }); + + test(`extend delays the expiration`, () => { + const { notifications, http } = coreMock.createSetup(); + const sessionExpired = createSessionExpiredMock(); + const sessionTimeout = new SessionTimeout(2 * 60 * 1000, notifications, sessionExpired, http); + + sessionTimeout.extend(); + jest.advanceTimersByTime(114 * 1000); + + sessionTimeout.extend(); + jest.advanceTimersByTime(114 * 1000); + expect(sessionExpired.logout).not.toHaveBeenCalled(); + + jest.advanceTimersByTime(1 * 1000); + expect(sessionExpired.logout).toHaveBeenCalled(); + }); + + test(`if the session timeout is shorter than 5 seconds, expire session immediately`, () => { + const { notifications, http } = coreMock.createSetup(); + const sessionExpired = createSessionExpiredMock(); + const sessionTimeout = new SessionTimeout(4 * 1000, notifications, sessionExpired, http); + + sessionTimeout.extend(); + jest.advanceTimersByTime(0); + expect(sessionExpired.logout).toHaveBeenCalled(); + }); + + test(`'null' sessionTimeout never logs you out`, () => { + const { notifications, http } = coreMock.createSetup(); + const sessionExpired = createSessionExpiredMock(); + const sessionTimeout = new SessionTimeout(null, notifications, sessionExpired, http); + sessionTimeout.extend(); + jest.advanceTimersByTime(Number.MAX_VALUE); + expect(sessionExpired.logout).not.toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/security/public/session/session_timeout.tsx b/x-pack/plugins/security/public/session/session_timeout.tsx new file mode 100644 index 0000000000000..db4926e7f04ea --- /dev/null +++ b/x-pack/plugins/security/public/session/session_timeout.tsx @@ -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 { NotificationsSetup, Toast, HttpSetup } from 'src/core/public'; +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { SessionTimeoutWarning } from './session_timeout_warning'; +import { ISessionExpired } from './session_expired'; + +/** + * Client session timeout is decreased by this number so that Kibana server + * can still access session content during logout request to properly clean + * user session up (invalidate access tokens, redirect to logout portal etc.). + */ +const GRACE_PERIOD_MS = 5 * 1000; + +/** + * Duration we'll normally display the warning toast + */ +const WARNING_MS = 60 * 1000; + +export interface ISessionTimeout { + extend(): void; +} + +export class SessionTimeout { + private warningTimeoutMilliseconds?: number; + private expirationTimeoutMilliseconds?: number; + private warningToast?: Toast; + + constructor( + private sessionTimeoutMilliseconds: number | null, + private notifications: NotificationsSetup, + private sessionExpired: ISessionExpired, + private http: HttpSetup + ) {} + + extend() { + if (this.sessionTimeoutMilliseconds == null) { + return; + } + + if (this.warningTimeoutMilliseconds) { + window.clearTimeout(this.warningTimeoutMilliseconds); + } + if (this.expirationTimeoutMilliseconds) { + window.clearTimeout(this.expirationTimeoutMilliseconds); + } + if (this.warningToast) { + this.notifications.toasts.remove(this.warningToast); + } + this.warningTimeoutMilliseconds = window.setTimeout( + () => this.showWarning(), + Math.max(this.sessionTimeoutMilliseconds - WARNING_MS - GRACE_PERIOD_MS, 0) + ); + this.expirationTimeoutMilliseconds = window.setTimeout( + () => this.sessionExpired.logout(), + Math.max(this.sessionTimeoutMilliseconds - GRACE_PERIOD_MS, 0) + ); + } + + private showWarning = () => { + this.warningToast = this.notifications.toasts.add({ + color: 'warning', + text: , + title: i18n.translate('xpack.security.components.sessionTimeoutWarning.title', { + defaultMessage: 'Warning', + }), + toastLifeTimeMs: Math.min(this.sessionTimeoutMilliseconds! - GRACE_PERIOD_MS, WARNING_MS), + }); + }; + + private refreshSession = () => { + this.http.get('/api/security/v1/me'); + }; +} 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 new file mode 100644 index 0000000000000..ffbd625590b15 --- /dev/null +++ b/x-pack/plugins/security/public/session/session_timeout_http_interceptor.test.ts @@ -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. + */ + +// @ts-ignore +import fetchMock from 'fetch-mock/es5/client'; +import { SessionTimeoutHttpInterceptor } from './session_timeout_http_interceptor'; +import { setup } from '../../../../../src/test_utils/public/http_test_setup'; +import { createSessionTimeoutMock } from './session_timeout.mock'; + +const mockCurrentUrl = (url: string) => window.history.pushState({}, '', url); + +const setupHttp = (basePath: string) => { + const { http } = setup(injectedMetadata => { + injectedMetadata.getBasePath.mockReturnValue(basePath); + }); + return http; +}; + +afterEach(() => { + fetchMock.restore(); +}); + +describe('response', () => { + test('extends session timeouts', async () => { + const http = setupHttp('/foo'); + const sessionTimeoutMock = createSessionTimeoutMock(); + const interceptor = new SessionTimeoutHttpInterceptor(sessionTimeoutMock, http.anonymousPaths); + http.intercept(interceptor); + fetchMock.mock('*', 200); + + await http.fetch('/foo-api'); + + expect(sessionTimeoutMock.extend).toHaveBeenCalled(); + }); + + test(`doesn't extend session timeouts for anonymous paths`, async () => { + mockCurrentUrl('/foo/bar'); + const http = setupHttp('/foo'); + const sessionTimeoutMock = createSessionTimeoutMock(); + const { anonymousPaths } = http; + anonymousPaths.register('/bar'); + const interceptor = new SessionTimeoutHttpInterceptor(sessionTimeoutMock, anonymousPaths); + http.intercept(interceptor); + fetchMock.mock('*', 200); + + await http.fetch('/foo-api'); + + expect(sessionTimeoutMock.extend).not.toHaveBeenCalled(); + }); + + test(`doesn't extend session timeouts for system api requests`, async () => { + const http = setupHttp('/foo'); + const sessionTimeoutMock = createSessionTimeoutMock(); + const interceptor = new SessionTimeoutHttpInterceptor(sessionTimeoutMock, http.anonymousPaths); + http.intercept(interceptor); + fetchMock.mock('*', 200); + + await http.fetch('/foo-api', { headers: { 'kbn-system-api': 'true' } }); + + expect(sessionTimeoutMock.extend).not.toHaveBeenCalled(); + }); +}); + +describe('responseError', () => { + test('extends session timeouts', async () => { + const http = setupHttp('/foo'); + const sessionTimeoutMock = createSessionTimeoutMock(); + const interceptor = new SessionTimeoutHttpInterceptor(sessionTimeoutMock, http.anonymousPaths); + http.intercept(interceptor); + fetchMock.mock('*', 401); + + await expect(http.fetch('/foo-api')).rejects.toMatchInlineSnapshot(`[Error: Unauthorized]`); + + expect(sessionTimeoutMock.extend).toHaveBeenCalled(); + }); + + test(`doesn't extend session timeouts for anonymous paths`, async () => { + mockCurrentUrl('/foo/bar'); + const http = setupHttp('/foo'); + const sessionTimeoutMock = createSessionTimeoutMock(); + const { anonymousPaths } = http; + anonymousPaths.register('/bar'); + const interceptor = new SessionTimeoutHttpInterceptor(sessionTimeoutMock, anonymousPaths); + http.intercept(interceptor); + fetchMock.mock('*', 401); + + await expect(http.fetch('/foo-api')).rejects.toMatchInlineSnapshot(`[Error: Unauthorized]`); + + expect(sessionTimeoutMock.extend).not.toHaveBeenCalled(); + }); + + test(`doesn't extend session timeouts for system api requests`, async () => { + const http = setupHttp('/foo'); + const sessionTimeoutMock = createSessionTimeoutMock(); + const interceptor = new SessionTimeoutHttpInterceptor(sessionTimeoutMock, http.anonymousPaths); + http.intercept(interceptor); + fetchMock.mock('*', 401); + + await expect( + http.fetch('/foo-api', { headers: { 'kbn-system-api': 'true' } }) + ).rejects.toMatchInlineSnapshot(`[Error: Unauthorized]`); + + expect(sessionTimeoutMock.extend).not.toHaveBeenCalled(); + }); + + test(`doesn't extend session timeouts when there is no response`, async () => { + const http = setupHttp('/foo'); + const sessionTimeoutMock = createSessionTimeoutMock(); + const interceptor = new SessionTimeoutHttpInterceptor(sessionTimeoutMock, http.anonymousPaths); + http.intercept(interceptor); + fetchMock.mock('*', new Promise((resolve, reject) => reject(new Error('Network is down')))); + + await expect(http.fetch('/foo-api')).rejects.toMatchInlineSnapshot(`[Error: Network is down]`); + + expect(sessionTimeoutMock.extend).not.toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/security/public/session/session_timeout_http_interceptor.ts b/x-pack/plugins/security/public/session/session_timeout_http_interceptor.ts new file mode 100644 index 0000000000000..98516cb4a613b --- /dev/null +++ b/x-pack/plugins/security/public/session/session_timeout_http_interceptor.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 { HttpInterceptor, HttpErrorResponse, HttpResponse, IAnonymousPaths } from 'src/core/public'; + +import { ISessionTimeout } from './session_timeout'; + +const isSystemAPIRequest = (request: Request) => { + return request.headers.has('kbn-system-api'); +}; + +export class SessionTimeoutHttpInterceptor implements HttpInterceptor { + constructor(private sessionTimeout: ISessionTimeout, private anonymousPaths: IAnonymousPaths) {} + + response(httpResponse: HttpResponse) { + if (this.anonymousPaths.isAnonymous(window.location.pathname)) { + return; + } + + if (isSystemAPIRequest(httpResponse.request)) { + return; + } + + this.sessionTimeout.extend(); + } + + responseError(httpErrorResponse: HttpErrorResponse) { + if (this.anonymousPaths.isAnonymous(window.location.pathname)) { + return; + } + + if (isSystemAPIRequest(httpErrorResponse.request)) { + return; + } + + // if we happen to not have a response, for example if there is no + // network connectivity, we won't extend the session because there + // won't be a response with a set-cookie header, which is required + // to extend the session + const { response } = httpErrorResponse; + if (!response) { + return; + } + + this.sessionTimeout.extend(); + } +} diff --git a/x-pack/legacy/plugins/security/public/components/session_expiration_warning/session_expiration_warning.test.tsx b/x-pack/plugins/security/public/session/session_timeout_warning.test.tsx similarity index 74% rename from x-pack/legacy/plugins/security/public/components/session_expiration_warning/session_expiration_warning.test.tsx rename to x-pack/plugins/security/public/session/session_timeout_warning.test.tsx index abc5a970eec9a..a52e7ce4e94b5 100644 --- a/x-pack/legacy/plugins/security/public/components/session_expiration_warning/session_expiration_warning.test.tsx +++ b/x-pack/plugins/security/public/session/session_timeout_warning.test.tsx @@ -5,12 +5,12 @@ */ import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import { SessionExpirationWarning } from './session_expiration_warning'; +import { SessionTimeoutWarning } from './session_timeout_warning'; -describe('SessionExpirationWarning', () => { +describe('SessionTimeoutWarning', () => { it('fires its callback when the OK button is clicked', () => { const handler = jest.fn(); - const wrapper = mountWithIntl(); + const wrapper = mountWithIntl(); expect(handler).toBeCalledTimes(0); wrapper.find('EuiButton[data-test-subj="refreshSessionButton"]').simulate('click'); diff --git a/x-pack/legacy/plugins/security/public/components/session_expiration_warning/session_expiration_warning.tsx b/x-pack/plugins/security/public/session/session_timeout_warning.tsx similarity index 81% rename from x-pack/legacy/plugins/security/public/components/session_expiration_warning/session_expiration_warning.tsx rename to x-pack/plugins/security/public/session/session_timeout_warning.tsx index 2b957e9b251a7..e1b4542031ed1 100644 --- a/x-pack/legacy/plugins/security/public/components/session_expiration_warning/session_expiration_warning.tsx +++ b/x-pack/plugins/security/public/session/session_timeout_warning.tsx @@ -12,12 +12,12 @@ interface Props { onRefreshSession: () => void; } -export const SessionExpirationWarning = (props: Props) => { +export const SessionTimeoutWarning = (props: Props) => { return ( <>

@@ -29,7 +29,7 @@ export const SessionExpirationWarning = (props: Props) => { data-test-subj="refreshSessionButton" > 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 new file mode 100644 index 0000000000000..60f032652221b --- /dev/null +++ b/x-pack/plugins/security/public/session/unauthorized_response_http_interceptor.test.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. + */ + +// @ts-ignore +import fetchMock from 'fetch-mock/es5/client'; +import { SessionExpired } from './session_expired'; +import { setup } from '../../../../../src/test_utils/public/http_test_setup'; +import { UnauthorizedResponseHttpInterceptor } from './unauthorized_response_http_interceptor'; +jest.mock('./session_expired'); + +const drainPromiseQueue = () => { + return new Promise(resolve => { + setImmediate(resolve); + }); +}; + +const mockCurrentUrl = (url: string) => window.history.pushState({}, '', url); + +const setupHttp = (basePath: string) => { + const { http } = setup(injectedMetadata => { + injectedMetadata.getBasePath.mockReturnValue(basePath); + }); + return http; +}; + +afterEach(() => { + fetchMock.restore(); +}); + +it(`logs out 401 responses`, async () => { + const http = setupHttp('/foo'); + const sessionExpired = new SessionExpired(http.basePath); + const logoutPromise = new Promise(resolve => { + jest.spyOn(sessionExpired, 'logout').mockImplementation(() => resolve()); + }); + const interceptor = new UnauthorizedResponseHttpInterceptor(sessionExpired, http.anonymousPaths); + http.intercept(interceptor); + fetchMock.mock('*', 401); + + let fetchResolved = false; + let fetchRejected = false; + http.fetch('/foo-api').then(() => (fetchResolved = true), () => (fetchRejected = true)); + + await logoutPromise; + await drainPromiseQueue(); + expect(fetchResolved).toBe(false); + expect(fetchRejected).toBe(false); +}); + +it(`ignores anonymous paths`, async () => { + mockCurrentUrl('/foo/bar'); + const http = setupHttp('/foo'); + const { anonymousPaths } = http; + anonymousPaths.register('/bar'); + const sessionExpired = new SessionExpired(http.basePath); + const interceptor = new UnauthorizedResponseHttpInterceptor(sessionExpired, anonymousPaths); + http.intercept(interceptor); + fetchMock.mock('*', 401); + + await expect(http.fetch('/foo-api')).rejects.toMatchInlineSnapshot(`[Error: Unauthorized]`); + expect(sessionExpired.logout).not.toHaveBeenCalled(); +}); + +it(`ignores errors which don't have a response, for example network connectivity issues`, async () => { + const http = setupHttp('/foo'); + const sessionExpired = new SessionExpired(http.basePath); + const interceptor = new UnauthorizedResponseHttpInterceptor(sessionExpired, http.anonymousPaths); + http.intercept(interceptor); + fetchMock.mock('*', new Promise((resolve, reject) => reject(new Error('Network is down')))); + + await expect(http.fetch('/foo-api')).rejects.toMatchInlineSnapshot(`[Error: Network is down]`); + expect(sessionExpired.logout).not.toHaveBeenCalled(); +}); + +it(`ignores requests which omit credentials`, async () => { + const http = setupHttp('/foo'); + const sessionExpired = new SessionExpired(http.basePath); + const interceptor = new UnauthorizedResponseHttpInterceptor(sessionExpired, http.anonymousPaths); + http.intercept(interceptor); + fetchMock.mock('*', 401); + + await expect(http.fetch('/foo-api', { credentials: 'omit' })).rejects.toMatchInlineSnapshot( + `[Error: Unauthorized]` + ); + expect(sessionExpired.logout).not.toHaveBeenCalled(); +}); diff --git a/x-pack/plugins/security/public/session/unauthorized_response_http_interceptor.ts b/x-pack/plugins/security/public/session/unauthorized_response_http_interceptor.ts new file mode 100644 index 0000000000000..a0ef2fdb86b47 --- /dev/null +++ b/x-pack/plugins/security/public/session/unauthorized_response_http_interceptor.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 { + HttpInterceptor, + HttpErrorResponse, + IHttpInterceptController, + IAnonymousPaths, +} from 'src/core/public'; + +import { SessionExpired } from './session_expired'; + +export class UnauthorizedResponseHttpInterceptor implements HttpInterceptor { + constructor(private sessionExpired: SessionExpired, private anonymousPaths: IAnonymousPaths) {} + + responseError(httpErrorResponse: HttpErrorResponse, controller: IHttpInterceptController) { + if (this.anonymousPaths.isAnonymous(window.location.pathname)) { + return; + } + + // if the request was omitting credentials it's to an anonymous endpoint + // (for example to login) and we don't wish to ever redirect + if (httpErrorResponse.request.credentials === 'omit') { + return; + } + + // if we happen to not have a response, for example if there is no + // network connectivity, we don't do anything + const { response } = httpErrorResponse; + if (!response) { + return; + } + + if (response.status === 401) { + this.sessionExpired.logout(); + controller.halt(); + } + } +} diff --git a/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.test.ts b/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.test.ts index 2e19f85875616..c2bc534f742a8 100644 --- a/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.test.ts +++ b/x-pack/plugins/spaces/server/lib/saved_objects_client/spaces_saved_objects_client.test.ts @@ -7,13 +7,13 @@ import { DEFAULT_SPACE_ID } from '../../../common/constants'; import { SpacesSavedObjectsClient } from './spaces_saved_objects_client'; import { spacesServiceMock } from '../../spaces_service/spaces_service.mock'; -import { SavedObjectsClientMock } from '../../../../../../src/core/server/mocks'; +import { savedObjectsClientMock } from '../../../../../../src/core/server/mocks'; const types = ['foo', 'bar', 'space']; const createMockRequest = () => ({}); -const createMockClient = () => SavedObjectsClientMock.create(); +const createMockClient = () => savedObjectsClientMock.create(); const createSpacesService = async (spaceId: string) => { return spacesServiceMock.createSetupContract(spaceId); diff --git a/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.test.ts b/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.test.ts index 92744aa9f0fc5..54d9654005f89 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.test.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.test.ts @@ -337,7 +337,7 @@ describe('copy to space', () => { expect(() => resolveConflicts.routeValidation.body!.validate(payload) ).toThrowErrorMatchingInlineSnapshot( - `"[key(\\"invalid-space-id!@#$%^&*()\\")]: Invalid space id: invalid-space-id!@#$%^&*()"` + `"[retries.key(\\"invalid-space-id!@#$%^&*()\\")]: Invalid space id: invalid-space-id!@#$%^&*()"` ); }); diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 4ff1459637889..55f3fdf1087e4 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -854,7 +854,6 @@ "data.search.searchBar.savedQueryPopoverSavedQueryListItemSelectedButtonAriaLabel": "選択されたクエリボタン {savedQueryName} を保存しました。変更を破棄するには押してください。", "data.search.searchBar.savedQueryPopoverTitleText": "保存されたクエリ", "embeddableApi.actions.applyFilterActionTitle": "現在のビューにフィルターを適用", - "embeddableApi.addPanel.createNew": "新規 {factoryName} を作成", "embeddableApi.addPanel.createNewDefaultOption": "新規作成...", "embeddableApi.addPanel.displayName": "パネルの追加", "embeddableApi.addPanel.savedObjectAddedToContainerSuccessMessageTitle": "{savedObjectName} が追加されました", @@ -3944,148 +3943,6 @@ "xpack.canvas.functions.urlparam.args.defaultHelpText": "{URL} パラメーターが指定されていないときに戻される文字列です。", "xpack.canvas.functions.urlparam.args.paramHelpText": "取得する {URL} ハッシュパラメーターです。", "xpack.canvas.functions.urlparamHelpText": "表現で使用する {URL} パラメーターを取得します。{urlparamFn} 関数は常に {TYPE_STRING} を戻します。たとえば、値 {value} を {URL} {example} のパラメーター {myVar} から取得できます)。", - "xpack.code.featureRegistry.codeFeatureName": "コード", - "xpack.code.adminPage.langserverTab.installedText": "インストール済み", - "xpack.code.adminPage.langserverTab.languageServersDescription": "{serverCount} 個の {serverCount, plural, one {言語サーバー} other {言語サーバー}}", - "xpack.code.adminPage.langserverTab.notInstalledText": "未インストール", - "xpack.code.adminPage.langserverTab.runningText": "実行中...", - "xpack.code.adminPage.langserverTab.setup.closeButtonLabel": "閉じる", - "xpack.code.adminPage.langserverTab.setup.installationInstructionTitle": "インストール手順", - "xpack.code.adminPage.langserverTab.setup.installTitle": "インストール", - "xpack.code.adminPage.langserverTab.setup.stopKibanaDescription": "kibana コードノードを停止します。", - "xpack.code.adminPage.langserverTab.setup.uninstallTitle": "アンインストール", - "xpack.code.adminPage.langserverTab.setup.useFollowingCommandToInstallDescription": "次のコマンドで {name} 言語サーバーをインストールします。", - "xpack.code.adminPage.langserverTab.setup.useFollowingCommandToRemoveDescription": "次のコマンドで {name} 言語サーバーを削除します。", - "xpack.code.adminPage.langserverTab.setupButtonLabel": "セットアップ", - "xpack.code.adminPage.langserverTabLabel": "言語サーバー", - "xpack.code.adminPage.repoTab.cancelButtonLabel": "キャンセル", - "xpack.code.adminPage.repoTab.emptyRepo.importFirstRepositoryText": "最初のをインポートしましょう", - "xpack.code.adminPage.repoTab.emptyRepo.noRepositoryText": "まだレポジトリがありません", - "xpack.code.adminPage.repoTab.emptyRepo.viewSetupGuideButtonLabel": "セットアップガイドを表示", - "xpack.code.adminPage.repoTab.importButtonLabel": "インポート", - "xpack.code.adminPage.repoTab.importRepoButtonLabel": "新規レポジトリをインポート", - "xpack.code.adminPage.repoTab.importRepoTitle": "新規レポジトリをインポート", - "xpack.code.adminPage.repoTab.repoDescription": "{projectsCount} 件の{projectsCount, plural, one {レポジトリ} other {レポジトリ}}", - "xpack.code.adminPage.repoTab.repositoryUrlEmptyText": "URL は未入力のままにできません。", - "xpack.code.adminPage.repoTab.repositoryUrlFormLabel": "レポジトリ URL", - "xpack.code.adminPage.repoTab.repositoryUrlTitle": "レポジトリ URL", - "xpack.code.adminPage.repoTab.sort.aToZDropDownOptionLabel": "A ~ Z", - "xpack.code.adminPage.repoTab.sort.sortByFormLabel": "並べ替え基準", - "xpack.code.adminPage.repoTab.sort.updatedAscDropDownOptionLabel": "最終更新 昇順", - "xpack.code.adminPage.repoTab.sort.updatedDescDropDownOptionLabel": "最終更新 降順", - "xpack.code.adminPage.repoTab.sort.zToADropDownOptionLabel": "Z ~ A", - "xpack.code.adminPage.repoTabLabel": "レポジトリ", - "xpack.code.adminPage.setupGuide.addRepositoryDescription": "{sampleRepoLink} または {ownRepoLink} をインポートします。GIt クローン URL をコードにコピー・貼り付けするだけです。", - "xpack.code.adminPage.setupGuide.addRepositoryOwnRepoLinkText": "独自のレポジトリ", - "xpack.code.adminPage.setupGuide.addRepositorySampleRepoLinkText": "サンプルレポジトリ", - "xpack.code.adminPage.setupGuide.addRepositoryTitle": "レポジトリをコードに追加", - "xpack.code.adminPage.setupGuide.backToDashboardButtonLabel": "レポジトリダッシュボードに戻る", - "xpack.code.adminPage.setupGuide.checkMultiInstanceDescription1": "単一の Kibana インスタンスを使用している場合は、このステップをスキップできます。", - "xpack.code.adminPage.setupGuide.checkMultiInstanceDescription2": "複数 Kibana インスタンスを使用している場合は、1 つのインスタンスを「コードノード」として割り当てる必要があります。「コードノード」を割り当てるには、次のコードをすべての Kibana インスタンスの kibana.yml ファイルに追加して、インスタンスを再起動します:", - "xpack.code.adminPage.setupGuide.checkMultiInstanceDescription3": "「$YourCodeNoteAddress」は、他の Kibana からアクセスできるよう割り当てられたコードノードの URL です。", - "xpack.code.adminPage.setupGuide.checkMultiInstanceTitle": "複数 Kibana インスタンスがクラスター URL として使用されているか確認してください。", - "xpack.code.adminPage.setupGuide.getStartedTitle": "Elastic Code を使いはじめる", - "xpack.code.adminPage.setupGuide.installExtraLangSupportDescription1": "対応言語と言語サーバーのインストールに関する詳細は、{link} をご覧ください。", - "xpack.code.adminPage.setupGuide.installExtraLangSupportDescription2": "Java 言語サポートが必要な場合は、インストール された言語サーバー {link} を管理できます。", - "xpack.code.adminPage.setupGuide.installExtraLangSupportHereLinkText": "こちら", - "xpack.code.adminPage.setupGuide.installExtraLangSupportTitle": "オプションで追加の言語サーバーをインストールすることができます", - "xpack.code.adminPage.setupGuide.learnMoreButtonLabel": "さらに詳しく", - "xpack.code.adminPage.setupGuide.permissionChangesDescription": "Kibana のロールとパーミッションにいくつかの変更点があります。これらの変更点が Code の導入にどのような影響を与えるかは、以下の情報をご覧ください。", - "xpack.code.adminPage.setupGuide.permissionChangesTitle": "パーミッションの変更", - "xpack.code.adminPage.setupGuide.verifyImportDescription": "レポジトリを {searchingLink} および {navigatingLink} することで、レポジトリが正しくインポートされたか確認できます。レポジトリに言語サポートが利用可能な場合は、{semanticNavigationLink} も利用可能であることを確認してください。", - "xpack.code.adminPage.setupGuide.verifyImportNavigatingLinkText": "移動", - "xpack.code.adminPage.setupGuide.verifyImportSearchingLinkText": "検索", - "xpack.code.adminPage.setupGuide.verifyImportSemanticNavigatingLinkText": "セマンティックナビゲーション", - "xpack.code.adminPage.setupGuide.verifyImportTitle": "レポジトリが正常にインポートされていることを確認してください", - "xpack.code.git.diskWatermarkLowMessage": "ディスクの空き容量が {watermark} 未満です", - "xpack.code.git.diskWatermarkLowPercentageMessage": "ディスクの使用レベルが {watermark} を超えています", - "xpack.code.gitUrlUtil.protocolNotWhitelistedMessage": "Git URL プロトコルがホワイトリストに登録されていません。", - "xpack.code.gitUrlUtil.urlNotWhitelistedMessage": "Git URL ホストがホワイトリストに登録されていません。", - "xpack.code.helpMenu.codeDocumentationButtonLabel": "Code のドキュメンテーション", - "xpack.code.helpMenu.helpDescription": "Code に関する情報", - "xpack.code.helpMenu.setupGuideButtonLabel": "セットアップガイド", - "xpack.code.mainPage.content.buttons.blameButtonLabel": "Blame", - "xpack.code.mainPage.content.buttons.codeButtonLabel": "コード", - "xpack.code.mainPage.content.buttons.contentButtonLabel": "コンテンツ", - "xpack.code.mainPage.content.buttons.downloadButtonLabel": "ダウンロード", - "xpack.code.mainPage.content.buttons.folderButtonLabel": "ディレクトリ", - "xpack.code.mainPage.content.buttons.historyButtonLabel": "履歴", - "xpack.code.mainPage.content.buttons.rawButtonLabel": "生", - "xpack.code.mainPage.content.cloneStatus.isCloningText": "クローンを作成中", - "xpack.code.mainPage.content.cloneStatus.progress.cloneFailedText": "クローンの作成に失敗", - "xpack.code.mainPage.content.cloneStatus.progress.indexingText": "オブジェクトをインデックス中: {progressRate}% {indexedObjects}/{totalObjects}", - "xpack.code.mainPage.content.cloneStatus.progress.receivingRateOnlyText": "オブジェクトを受信中: {progressRate}%", - "xpack.code.mainPage.content.cloneStatus.progress.receivingText": "オブジェクトを受信中: {progressRate}% {receivedObjects}/{totalObjects}", - "xpack.code.mainPage.content.cloneStatus.yourProjectWillBeAvailableText": "プロジェクトはこのプロセスの完了時に利用可能になります", - "xpack.code.mainPage.content.directory.directoriesTitle": "指示", - "xpack.code.mainPage.content.directory.filesTitle": "ファイル", - "xpack.code.mainPage.directory.recentCommitsTitle": "最近のコミット", - "xpack.code.mainPage.directory.viewAllCommitsButtonLabel": "すべて表示", - "xpack.code.mainPage.history.commitHistoryTitle": "コミット履歴", - "xpack.code.mainPage.history.commitsOnTitle": "{date} のコミット", - "xpack.code.mainPage.history.moreButtonLabel": "もっと", - "xpack.code.mainPage.sideTab.fileTabLabel": "ファイル", - "xpack.code.mainPage.sideTab.languageServerInitializingText": "言語サーバーを初期化中", - "xpack.code.mainPage.sideTab.loadingFileTreeText": "ファイルツリーを読み込み中", - "xpack.code.mainPage.sideTab.loadingStructureTreeText": "ストラクチャツリーを読み込み中", - "xpack.code.mainPage.sideTab.structureTabLabel": "ストラクチャ", - "xpack.code.monaco.hover.findReferenceButtonLabel": "リファレンスを検索", - "xpack.code.monaco.hover.gotoDefinitionButtonLabel": "Goto 定義", - "xpack.code.monaco.hover.languageServerInitializingDescription": "レポジトリのサイズによって、この操作には数分かかる場合があります。", - "xpack.code.monaco.hover.languageServerInitializingTitle": "言語サーバーを初期化中…", - "xpack.code.repoFileStatus.fileIsTooBigMessage": "現在のファイルは大きすぎます。", - "xpack.code.repoFileStatus.fileNotSupportedMessage": "現在のファイルは対応言語のものではありません。", - "xpack.code.repoFileStatus.IndexingInProgressMessage": "インデックス中です。", - "xpack.code.repoFileStatus.langServerNotInstalledMessage": "現在のファイルをサポートするには、追加言語サーバーをインストールしてください。", - "xpack.code.repoFileStatus.langserverType.dedicatedMessage": "現在のファイルには専用の言語サーバーが使用されます。", - "xpack.code.repoFileStatus.langserverType.genericMessage": "現在のファイルには一般の言語サーバーのみ使用できます。", - "xpack.code.repoFileStatus.langserverType.noneMessage": "現在のファイルはどの言語サーバーにもサポートされていません。", - "xpack.code.repoFileStatus.languageServerInitializedMessage": "言語サーバーが初期化されました。", - "xpack.code.repoFileStatus.langugageServerIsInitializitingMessage": "言語サーバーを初期化中です。", - "xpack.code.repoFileStatus.revisionNotIndexedMessage": "現在のリビジョンはインデックスされていません。", - "xpack.code.repoItem.cancelButtonText": "いいえ、削除しません", - "xpack.code.repoItem.cloneErrorText": "レポジトリのクローン作成中にエラーが発生", - "xpack.code.repoItem.cloningText": "クローンを作成中…", - "xpack.code.repoItem.confirmButtonText": "はい、削除します", - "xpack.code.repoItem.deleteButtonLabel": "削除", - "xpack.code.repoItem.deleteConfirmTitle": "このレポジトリを削除しますか?", - "xpack.code.repoItem.deleteErrorText": "レポジトリの削除中にエラーが発生", - "xpack.code.repoItem.deletingText": "削除中…", - "xpack.code.repoItem.indexErrorText": "レポジトリのインデックス中にエラーが発生", - "xpack.code.repoItem.indexingText": "インデックス中…", - "xpack.code.repoItem.initText": "初期化…", - "xpack.code.repoItem.lastUpdatedText": "最終更新", - "xpack.code.repoItem.reindexButtonLabel": "再インデックス", - "xpack.code.repoItem.reindexConfirmTitle": "このレポジトリを再インデックスしますか?", - "xpack.code.repoItem.settingsButtonLabel": "設定", - "xpack.code.repositoryManagement.repoImportedMessage": "このレポジトリは既にインポート済みです!", - "xpack.code.repositoryManagement.repoSubmittedMessage": "{name} が送信されました!", - "xpack.code.searchBar.addRepoPlaceholder": "レポジトリを追加するために検索", - "xpack.code.searchBar.addToScopeDescription": "インデックス済みのレポジトリを検索対象に追加します", - "xpack.code.searchBar.applyAndCloseButtonLabel": "適用して閉じる", - "xpack.code.searchBar.fileGroupTitle": "ファイル", - "xpack.code.searchBar.repositorylGroupTitle": "レポジトリ", - "xpack.code.searchBar.resultCountText": "{total} 件の{total, plural, one {結果} other {結果}}", - "xpack.code.searchBar.searchFilterTitle": "{filterCount, plural, one {検索フィルター} other {検索フィルター}}", - "xpack.code.searchBar.searchScopeTitle": "検索対象", - "xpack.code.searchBar.symbolGroupTitle": "シンボル", - "xpack.code.searchBar.viewFullSearchLinkText": "完全テキスト全文検索は ⮐ エンターキーを押してください", - "xpack.code.searchBar.viewMoreLinkText": "他を表示", - "xpack.code.searchPage.emptyText": "他を検索したり、検索設定を変更したりできます。", - "xpack.code.searchPage.emptyTitle": "ふむむ、検索しましたが何も見つかりませんでした。", - "xpack.code.searchPage.hitsCountText": " 次の場所からのヒット ", - "xpack.code.searchPage.modifySearchSettingButtonLabel": "検索設定を変更", - "xpack.code.searchPage.showingResultsTitle": "{total} 件中 {from} ~ {to} 件目を表示中", - "xpack.code.searchPage.sideBar.languagesTitle": "言語", - "xpack.code.searchPage.sideBar.repositoriesTitle": "レポジトリ", - "xpack.code.searchScope.defaultDropDownOptionLabel": "すべて検索", - "xpack.code.searchScope.defaultPlaceholder": "入力してすべてを検索", - "xpack.code.searchScope.fileDropDownOptionLabel": "ファイルを検索", - "xpack.code.searchScope.filePlaceholder": "入力してファイルを検索", - "xpack.code.searchScope.repositoryDropDownOptionLabel": "レポジトリを検索", - "xpack.code.searchScope.repositoryPlaceholder": "入力してレポジトリを検索", - "xpack.code.searchScope.symbolDropDownOptionLabel": "シンボルを検索", - "xpack.code.searchScope.symbolPlaceholder": "入力してシンボルを検索", "xpack.crossClusterReplication.addAutoFollowPatternButtonLabel": "自動フォローパターンを作成", "xpack.crossClusterReplication.addBreadcrumbTitle": "追加", "xpack.crossClusterReplication.addFollowerButtonLabel": "フォロワーインデックスを作成", @@ -8549,9 +8406,9 @@ "xpack.security.account.passwordsDoNotMatch": "パスワードが一致していません。", "xpack.security.account.usernameGroupDescription": "この情報は変更できません。", "xpack.security.account.usernameGroupTitle": "ユーザー名とメールアドレス", - "xpack.security.components.sessionExpiration.logoutNotification": "操作がないため間もなくログアウトします。再開するには [OK] をクリックしてくださ。", - "xpack.security.components.sessionExpiration.okButtonText": "OK", - "xpack.security.hacks.warningTitle": "警告", + "xpack.security.components.sessionTimeoutWarning.message": "操作がないため間もなくログアウトします。再開するには [OK] をクリックしてくださ。", + "xpack.security.components.sessionTimeoutWarning.okButtonText": "OK", + "xpack.security.components.sessionTimeoutWarning.title": "警告", "xpack.security.loggedOut.login": "ログイン", "xpack.security.loggedOut.title": "ログアウト完了", "xpack.security.login.basicLoginForm.invalidUsernameOrPasswordErrorMessage": "無効なユーザー名またはパスワード再試行してください。", @@ -9318,7 +9175,6 @@ "xpack.siem.anomaliesTable.table.unit": "{totalCount, plural, =1 {anomaly} other {anomalies}}", "xpack.siem.clipboard.copy.to.the.clipboard": "クリップボードにコピー", "xpack.siem.components.embeddables.indexPatternsMissingPrompt.errorButtonLabel": "インデックスパターンを編集", - "xpack.siem.components.embeddables.indexPatternsMissingPrompt.errorDescription": "マップにイベントデータを表示するには、ECS に準拠した Kibana インデックスパターンを構成する必要があります。ビートを使用すると、次のセットアップコマンドを実行して必要な Kibana インデックスパターンを作成するか、Kibana の設定で手動により構成することができます。", "xpack.siem.components.embeddables.indexPatternsMissingPrompt.errorTitle": "必要なインデックスパターンが構成されていません", "xpack.siem.components.ml.anomaly.errors.anomaliesTableFetchFailureTitle": "異常表の取得に失敗", "xpack.siem.components.ml.api.errors.networkErrorFailureTitle": "ネットワークエラー", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 116a1f15a139b..f822e90ec3973 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -855,7 +855,6 @@ "data.search.searchBar.savedQueryPopoverSavedQueryListItemSelectedButtonAriaLabel": "已保存查询按钮已选择 {savedQueryName}。按下可清除任何更改。", "data.search.searchBar.savedQueryPopoverTitleText": "已保存查询", "embeddableApi.actions.applyFilterActionTitle": "将筛选应用于当前视图", - "embeddableApi.addPanel.createNew": "创建新的{factoryName}", "embeddableApi.addPanel.createNewDefaultOption": "创建新的......", "embeddableApi.addPanel.displayName": "添加面板", "embeddableApi.addPanel.savedObjectAddedToContainerSuccessMessageTitle": "{savedObjectName} 已添加", @@ -3945,150 +3944,6 @@ "xpack.canvas.functions.urlparam.args.defaultHelpText": "未指定 {URL} 参数时返回的值。", "xpack.canvas.functions.urlparam.args.paramHelpText": "要检索的 {URL} 哈希参数。", "xpack.canvas.functions.urlparamHelpText": "检索要在表达式中使用的 {URL} 参数。{urlparamFn} 函数始终返回 {TYPE_STRING}。例如,从 {URL} 的参数 {myVar} 检索值 {value} ({example})。", - "xpack.code.adminPage.langserverTab.installedText": "已安装", - "xpack.code.adminPage.langserverTab.languageServersDescription": "{serverCount} 个 {serverCount, plural, one {语言服务器} other {语言服务器}}", - "xpack.code.adminPage.langserverTab.notInstalledText": "未安装", - "xpack.code.adminPage.langserverTab.runningText": "正在运行......", - "xpack.code.adminPage.langserverTab.setup.closeButtonLabel": "关闭", - "xpack.code.adminPage.langserverTab.setup.installationInstructionTitle": "安装说明", - "xpack.code.adminPage.langserverTab.setup.installTitle": "安装", - "xpack.code.adminPage.langserverTab.setup.stopKibanaDescription": "停止您的 Kibana Code 节点。", - "xpack.code.adminPage.langserverTab.setup.uninstallTitle": "卸载", - "xpack.code.adminPage.langserverTab.setup.useFollowingCommandToInstallDescription": "使用以下命令安装 {name} 语言服务器。", - "xpack.code.adminPage.langserverTab.setup.useFollowingCommandToRemoveDescription": "使用以下命令卸载 {name} 语言服务器。", - "xpack.code.adminPage.langserverTab.setupButtonLabel": "设置", - "xpack.code.adminPage.langserverTabLabel": "语言服务器", - "xpack.code.adminPage.repoTab.cancelButtonLabel": "取消", - "xpack.code.adminPage.repoTab.emptyRepo.importFirstRepositoryText": "让我们导入您的首个存储库", - "xpack.code.adminPage.repoTab.emptyRepo.noRepositoryText": "您尚未有任何存储库", - "xpack.code.adminPage.repoTab.emptyRepo.viewSetupGuideButtonLabel": "查看设置指南", - "xpack.code.adminPage.repoTab.importButtonLabel": "导入", - "xpack.code.adminPage.repoTab.importRepoButtonLabel": "导入新的存储库", - "xpack.code.adminPage.repoTab.importRepoTitle": "导入新的存储库", - "xpack.code.adminPage.repoTab.repoDescription": "{projectsCount} 个 {projectsCount, plural, one {存储库} other {存储库}}", - "xpack.code.adminPage.repoTab.repositoryUrlEmptyText": "URL 不应为空。", - "xpack.code.adminPage.repoTab.repositoryUrlFormLabel": "存储库 URL", - "xpack.code.adminPage.repoTab.repositoryUrlTitle": "存储库 URL", - "xpack.code.adminPage.repoTab.sort.aToZDropDownOptionLabel": "升序", - "xpack.code.adminPage.repoTab.sort.sortByFormLabel": "排序依据", - "xpack.code.adminPage.repoTab.sort.updatedAscDropDownOptionLabel": "最后更新时间 - 升序", - "xpack.code.adminPage.repoTab.sort.updatedDescDropDownOptionLabel": "最后更新时间 - 降序", - "xpack.code.adminPage.repoTab.sort.zToADropDownOptionLabel": "降序", - "xpack.code.adminPage.repoTabLabel": "存储库", - "xpack.code.adminPage.setupGuide.checkMultiInstanceTitle": "确认多个 Kibana 实例是否用作集群 URL", - "xpack.code.adminPage.setupGuide.checkMultiInstanceDescription1": "如果使用单个 Kibana 实例,则可以跳过此步骤。", - "xpack.code.adminPage.setupGuide.checkMultiInstanceDescription2": "如果使用多个 Kibana 实例,则需要将一个 Kibana 实例指定为 `Code 节点`。为此,将以下代码行添加到每个 Kibana 实例的 kibana.yml 文件中,并重新启动这些实例:", - "xpack.code.adminPage.setupGuide.checkMultiInstanceDescription3": "其中,`$YourCodeNoteAddress` 是已指定 Code 节点的 URL,其可由其他 Kibana 实例访问。", - "xpack.code.adminPage.setupGuide.installExtraLangSupportTitle": "根据需要安装额外的语言支持", - "xpack.code.adminPage.setupGuide.installExtraLangSupportDescription1": "访问{link},详细了解支持的语言和语言服务器安装。", - "xpack.code.adminPage.setupGuide.installExtraLangSupportDescription2": "如果需要 Java 语言支持,可以在{link}管理语言服务器安装。", - "xpack.code.adminPage.setupGuide.installExtraLangSupportHereLinkText": "此处", - "xpack.code.adminPage.setupGuide.addRepositoryTitle": "将存储库添加到 Code", - "xpack.code.adminPage.setupGuide.addRepositoryDescription": "导入 {sampleRepoLink} 或 {ownRepoLink}。只需将 git clone URL 复制并粘贴到 Code。", - "xpack.code.adminPage.setupGuide.addRepositorySampleRepoLinkText": "样例存储库", - "xpack.code.adminPage.setupGuide.addRepositoryOwnRepoLinkText": "您自己的存储库", - "xpack.code.adminPage.setupGuide.verifyImportTitle": "确认存储库成功导入", - "xpack.code.adminPage.setupGuide.verifyImportDescription": "您可以通过{searchingLink}和{navigatingLink}存储库来确认存储库是否成功导入。如果语言支持可用于存储库,请确保{semanticNavigationLink}也可用。", - "xpack.code.adminPage.setupGuide.verifyImportSearchingLinkText": "搜索", - "xpack.code.adminPage.setupGuide.verifyImportNavigatingLinkText": "导航", - "xpack.code.adminPage.setupGuide.verifyImportSemanticNavigatingLinkText": "语义导航", - "xpack.code.adminPage.setupGuide.backToDashboardButtonLabel": "返回存储库仪表板", - "xpack.code.adminPage.setupGuide.getStartedTitle": "Elastic Code 入门", - "xpack.code.adminPage.setupGuide.learnMoreButtonLabel": "了解详情", - "xpack.code.adminPage.setupGuide.permissionChangesDescription": "我们在 Kibana 中对角色和权限做了一些变更。在下面详细了解这些变更如何影响 Code 实施。", - "xpack.code.adminPage.setupGuide.permissionChangesTitle": "权限变更", - "xpack.code.featureRegistry.codeFeatureName": "Code", - "xpack.code.git.diskWatermarkLowMessage": "可用磁盘空间低于 {watermark}", - "xpack.code.git.diskWatermarkLowPercentageMessage": "磁盘使用水位线水平高于 {watermark}", - "xpack.code.gitUrlUtil.urlNotWhitelistedMessage": "Git url 主机未列入白名单。", - "xpack.code.gitUrlUtil.protocolNotWhitelistedMessage": "Git url 协议未列入白名单。", - "xpack.code.helpMenu.codeDocumentationButtonLabel": "Code 文档", - "xpack.code.helpMenu.helpDescription": "有关 Code 特定信息", - "xpack.code.helpMenu.setupGuideButtonLabel": "设置指南", - "xpack.code.mainPage.content.cloneStatus.isCloningText": "正在克隆", - "xpack.code.mainPage.content.cloneStatus.yourProjectWillBeAvailableText": "此过程完成时,您的项目将可用", - "xpack.code.mainPage.content.cloneStatus.progress.receivingText": "正在接收对象:{progressRate}% {receivedObjects}/{totalObjects}", - "xpack.code.mainPage.content.cloneStatus.progress.receivingRateOnlyText": "正在接收对象:{progressRate}%", - "xpack.code.mainPage.content.cloneStatus.progress.indexingText": "正在索引对象:{progressRate}% {indexedObjects}/{totalObjects}", - "xpack.code.mainPage.content.cloneStatus.progress.cloneFailedText": "克隆失败", - "xpack.code.mainPage.content.buttons.blameButtonLabel": "归咎", - "xpack.code.mainPage.content.buttons.codeButtonLabel": "Code", - "xpack.code.mainPage.content.buttons.contentButtonLabel": "内容", - "xpack.code.mainPage.content.buttons.downloadButtonLabel": "下载", - "xpack.code.mainPage.content.buttons.folderButtonLabel": "目录", - "xpack.code.mainPage.content.buttons.historyButtonLabel": "历史记录", - "xpack.code.mainPage.content.buttons.rawButtonLabel": "原始", - "xpack.code.mainPage.content.directory.directoriesTitle": "目录", - "xpack.code.mainPage.content.directory.filesTitle": "文件", - "xpack.code.mainPage.directory.recentCommitsTitle": "最近的提交", - "xpack.code.mainPage.directory.viewAllCommitsButtonLabel": "查看全部", - "xpack.code.mainPage.history.commitHistoryTitle": "提交历史记录", - "xpack.code.mainPage.history.commitsOnTitle": "{date} 的提交", - "xpack.code.mainPage.history.moreButtonLabel": "更多", - "xpack.code.mainPage.sideTab.fileTabLabel": "文件", - "xpack.code.mainPage.sideTab.languageServerInitializingText": "语言服务器正在初始化", - "xpack.code.mainPage.sideTab.loadingFileTreeText": "正在加载文件树", - "xpack.code.mainPage.sideTab.loadingStructureTreeText": "正在加载结构树", - "xpack.code.mainPage.sideTab.structureTabLabel": "结构", - "xpack.code.monaco.hover.findReferenceButtonLabel": "查找引用", - "xpack.code.monaco.hover.gotoDefinitionButtonLabel": "Goto 定义", - "xpack.code.monaco.hover.languageServerInitializingDescription": "这可能需要若干分钟,取决于存储库的大小。", - "xpack.code.monaco.hover.languageServerInitializingTitle": "语言服务器正在初始化……", - "xpack.code.repoItem.cancelButtonText": "不,不执行", - "xpack.code.repoItem.cloneErrorText": "克隆存储库时出错", - "xpack.code.repoItem.cloningText": "正在克隆......", - "xpack.code.repoItem.confirmButtonText": "是,执行", - "xpack.code.repoItem.deleteButtonLabel": "删除", - "xpack.code.repoItem.deleteConfirmTitle": "删除此存储库?", - "xpack.code.repoItem.deleteErrorText": "删除存储库时出错", - "xpack.code.repoItem.deletingText": "正在删除......", - "xpack.code.repoItem.indexErrorText": "索引存储库时出错", - "xpack.code.repoItem.indexingText": "正在索引......", - "xpack.code.repoItem.initText": "正在初始化...", - "xpack.code.repoItem.lastUpdatedText": "最后更新时间", - "xpack.code.repoItem.reindexButtonLabel": "重新索引", - "xpack.code.repoItem.reindexConfirmTitle": "重新索引此存储库?", - "xpack.code.repoItem.settingsButtonLabel": "设置", - "xpack.code.repositoryManagement.repoImportedMessage": "此存储库已导入!", - "xpack.code.repositoryManagement.repoOtherSpaceImportedMessage": "此存储库已在其他空间导入!", - "xpack.code.repositoryManagement.repoSubmittedMessage": "{name} 已成功提交!", - "xpack.code.repoFileStatus.langugageServerIsInitializitingMessage": "语言服务器正在初始化。", - "xpack.code.repoFileStatus.languageServerInitializedMessage": "语言服务器已初始化。", - "xpack.code.repoFileStatus.IndexingInProgressMessage": "索引正在进行中。", - "xpack.code.repoFileStatus.fileNotSupportedMessage": "当前文件的语言不受支持。", - "xpack.code.repoFileStatus.revisionNotIndexedMessage": "当前修订未索引。", - "xpack.code.repoFileStatus.langServerNotInstalledMessage": "安装其他语言服务器以支持当前文件。", - "xpack.code.repoFileStatus.fileIsTooBigMessage": "当前文件过大。", - "xpack.code.repoFileStatus.langserverType.noneMessage": "当前文件不受任何语言服务器支持。", - "xpack.code.repoFileStatus.langserverType.genericMessage": "当前文件仅由常规语言服务器支持。", - "xpack.code.repoFileStatus.langserverType.dedicatedMessage": "当前文件由专用语言服务器支持。", - "xpack.code.repoFileStatus.langServerLaunchFailed": "语言服务器启动失败。", - "xpack.code.searchBar.addRepoPlaceholder": "搜索以添加存储库", - "xpack.code.searchBar.addToScopeDescription": "将索引的存储库添加到搜索范围", - "xpack.code.searchBar.applyAndCloseButtonLabel": "应用并关闭", - "xpack.code.searchBar.fileGroupTitle": "文件", - "xpack.code.searchBar.repositorylGroupTitle": "存储库", - "xpack.code.searchBar.resultCountText": "{total} 个 {total, plural, one {结果} other {结果}}", - "xpack.code.searchBar.searchFilterTitle": "{filterCount, plural, one {高级选项} other {高级选项}}", - "xpack.code.searchBar.searchScopeTitle": "搜索范围", - "xpack.code.searchBar.symbolGroupTitle": "符号", - "xpack.code.searchBar.viewFullSearchLinkText": "按 ⮐ Return 键以进行全文本搜索", - "xpack.code.searchBar.viewMoreLinkText": "查看更多内容", - "xpack.code.searchPage.emptyText": "您可以搜索其他内容或修改搜索设置。", - "xpack.code.searchPage.emptyTitle": "嗯...... 我们已找过该内容,但未有任何发现。", - "xpack.code.searchPage.hitsCountText": " 次命中,来自 ", - "xpack.code.searchPage.modifySearchSettingButtonLabel": "修改搜索设置", - "xpack.code.searchPage.showingResultsTitle": "显示第 {from} - {to} 个结果,共 {total} 个。", - "xpack.code.searchPage.sideBar.languagesTitle": "语言", - "xpack.code.searchPage.sideBar.repositoriesTitle": "存储库", - "xpack.code.searchScope.defaultDropDownOptionLabel": "搜索所有内容", - "xpack.code.searchScope.defaultPlaceholder": "键入要查找的任何内容", - "xpack.code.searchScope.fileDropDownOptionLabel": "搜索文件", - "xpack.code.searchScope.filePlaceholder": "键入要查找的文件", - "xpack.code.searchScope.repositoryDropDownOptionLabel": "搜索存储库", - "xpack.code.searchScope.repositoryPlaceholder": "键入要查找的存储库", - "xpack.code.searchScope.symbolDropDownOptionLabel": "搜索符号", - "xpack.code.searchScope.symbolPlaceholder": "键入要查找的符号", "xpack.crossClusterReplication.addAutoFollowPatternButtonLabel": "创建自动跟随模式", "xpack.crossClusterReplication.addBreadcrumbTitle": "添加", "xpack.crossClusterReplication.addFollowerButtonLabel": "创建 Follower 索引", @@ -8706,9 +8561,9 @@ "xpack.security.account.passwordsDoNotMatch": "密码不匹配。", "xpack.security.account.usernameGroupDescription": "不能更改此信息。", "xpack.security.account.usernameGroupTitle": "用户名和电子邮件", - "xpack.security.components.sessionExpiration.logoutNotification": "由于处于不活动状态,您即将退出。单击“确定”可以恢复。", - "xpack.security.components.sessionExpiration.okButtonText": "确定", - "xpack.security.hacks.warningTitle": "警告", + "xpack.security.components.sessionTimeoutWarning.message": "由于处于不活动状态,您即将退出。单击“确定”可以恢复。", + "xpack.security.components.sessionTimeoutWarning.okButtonText": "确定", + "xpack.security.components.sessionTimeoutWarning.title": "警告", "xpack.security.loggedOut.login": "登录", "xpack.security.loggedOut.title": "已成功退出", "xpack.security.login.basicLoginForm.invalidUsernameOrPasswordErrorMessage": "用户名或密码无效。请重试。", @@ -9475,7 +9330,6 @@ "xpack.siem.anomaliesTable.table.unit": "{totalCount, plural, =1 {anomaly} other {anomalies}}", "xpack.siem.clipboard.copy.to.the.clipboard": "复制到剪贴板", "xpack.siem.components.embeddables.indexPatternsMissingPrompt.errorButtonLabel": "配置索引模式", - "xpack.siem.components.embeddables.indexPatternsMissingPrompt.errorDescription": "必须配置符合 ECS 的 Kibana 索引模式,才能查看地图上的数据。使用 Beats 时,您可以运行以下设置命令来创建所需的 Kibana 索引模式,否则只能在 Kibana 设置内手动配置。", "xpack.siem.components.embeddables.indexPatternsMissingPrompt.errorTitle": "未配置所需的索引模式", "xpack.siem.components.ml.anomaly.errors.anomaliesTableFetchFailureTitle": "异常表提取失败", "xpack.siem.components.ml.api.errors.networkErrorFailureTitle": "网络错误:", diff --git a/x-pack/scripts/mocha.js b/x-pack/scripts/mocha.js index 06d524fe526bf..cc0f94a2a760e 100644 --- a/x-pack/scripts/mocha.js +++ b/x-pack/scripts/mocha.js @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -require('../scripts/mocha'); +require('../../scripts/mocha'); diff --git a/x-pack/tasks/test.ts b/x-pack/tasks/test.ts index 7be64f7179f63..d26683899ce3f 100644 --- a/x-pack/tasks/test.ts +++ b/x-pack/tasks/test.ts @@ -17,10 +17,6 @@ export const testServerTask = async () => { const testGlobs = ['common/**/__tests__/**/*.js', 'server/**/__tests__/**/*.js']; - if (pluginIds.includes('code')) { - testGlobs.push(`legacy/plugins/**/server/**/__tests__/**/*.ts`); - } - for (const pluginId of pluginIds) { testGlobs.push( `legacy/plugins/${pluginId}/__tests__/**/*.js`, diff --git a/x-pack/test/api_integration/apis/code/feature_controls.ts b/x-pack/test/api_integration/apis/code/feature_controls.ts deleted file mode 100644 index a96b3074e4f81..0000000000000 --- a/x-pack/test/api_integration/apis/code/feature_controls.ts +++ /dev/null @@ -1,390 +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 expect from '@kbn/expect'; -import { SecurityService, SpacesService } from '../../../common/services'; -import { FtrProviderContext } from '../../ftr_provider_context'; - -export default function featureControlsTests({ getService }: FtrProviderContext) { - const supertest = getService('supertestWithoutAuth'); - const security: SecurityService = getService('security'); - const spaces: SpacesService = getService('spaces'); - - const expect404 = (result: any) => { - expect(result.error).to.be(undefined); - expect(result.response).not.to.be(undefined); - expect(result.response).to.have.property('statusCode', 404); - }; - - const expect200 = (result: any) => { - expect(result.error).to.be(undefined); - expect(result.response).not.to.be(undefined); - expect(result.response).to.have.property('statusCode', 200); - }; - - const endpoints = [ - { - // List all repositories. - url: `/api/code/repos`, - expectForbidden: expect404, - expectResponse: expect200, - }, - { - // Get one repository. - url: `/api/code/repo/github.com/elastic/code-examples_empty-file`, - expectForbidden: expect404, - expectResponse: expect200, - isRepoApi: true, - }, - { - // Get the status of one repository. - url: `/api/code/repo/status/github.com/elastic/code-examples_empty-file`, - expectForbidden: expect404, - expectResponse: expect200, - isRepoApi: true, - }, - { - // Get all language server installation status. - url: `/api/code/install`, - expectForbidden: expect404, - expectResponse: expect200, - }, - // Search and suggestion APIs. - { - url: `/api/code/search/repo?q=starter`, - expectForbidden: expect404, - expectResponse: expect200, - }, - { - url: `/api/code/suggestions/repo?q=starter`, - expectForbidden: expect404, - expectResponse: expect200, - }, - { - url: `/api/code/search/doc?q=starter`, - expectForbidden: expect404, - expectResponse: expect200, - }, - { - url: `/api/code/suggestions/doc?q=starter`, - expectForbidden: expect404, - expectResponse: expect200, - }, - { - url: `/api/code/search/symbol?q=starter`, - expectForbidden: expect404, - expectResponse: expect200, - }, - { - url: `/api/code/suggestions/symbol?q=starter`, - expectForbidden: expect404, - expectResponse: expect200, - }, - ]; - - async function executeRequest( - endpoint: string, - username: string, - password: string, - spaceId?: string - ) { - const basePath = spaceId ? `/s/${spaceId}` : ''; - - return await supertest - .get(`${basePath}${endpoint}`) - .auth(username, password) - .set('kbn-xsrf', 'foo') - .then((response: any) => ({ error: undefined, response })) - .catch((error: any) => ({ error, response: undefined })); - } - - async function executeRequests( - username: string, - password: string, - spaceId: string, - expectation: 'forbidden' | 'response', - skipRepoApi: boolean = false - ) { - for (const endpoint of endpoints) { - if (skipRepoApi && endpoint.isRepoApi === true) { - continue; - } - const result = await executeRequest(endpoint.url, username, password, spaceId); - if (expectation === 'forbidden') { - endpoint.expectForbidden(result); - } else { - endpoint.expectResponse(result); - } - } - } - - describe('feature controls', () => { - const codeAdminUsername = 'code_admin_user'; - const codeAdminRoleName = 'code_admin_role'; - const codeAdminUserPassword = `${codeAdminUsername}-password`; - - before(async () => { - await security.role.create(codeAdminRoleName, { - kibana: [ - { - feature: { - // Grant all permission to Code app as an admin user. - code: ['all'], - }, - spaces: ['*'], - }, - ], - }); - - // Import a repository first - await security.user.create(codeAdminUsername, { - password: codeAdminUserPassword, - roles: [codeAdminRoleName], - full_name: 'Code admin user', - }); - - await supertest - .post(`/api/code/repo`) - .auth(codeAdminUsername, codeAdminUserPassword) - .set('kbn-xsrf', 'foo') - .send({ url: 'https://github.com/elastic/code-examples_empty-file.git' }) - .expect(200); - }); - - after(async () => { - // Delete the repository - await supertest - .delete(`/api/code/repo/github.com/elastic/code-examples_empty-file`) - .auth(codeAdminUsername, codeAdminUserPassword) - .set('kbn-xsrf', 'foo') - .expect(200); - - await security.role.delete(codeAdminRoleName); - await security.user.delete(codeAdminUsername); - }); - - it(`Non admin Code user cannot execute delete without all permission`, async () => { - const username = 'logstash_read'; - const roleName = 'logstash_read'; - const password = `${username}-password`; - try { - await security.role.create(roleName, { - kibana: [ - { - feature: { - // Grant read only permission to Code app as an non-admin user. - code: ['read'], - }, - spaces: ['*'], - }, - ], - }); - - await security.user.create(username, { - password, - roles: [roleName], - full_name: 'a kibana user', - }); - - await supertest - .delete(`/api/code/repo/github.com/elastic/code-examples_empty-file`) - .auth(username, password) - .set('kbn-xsrf', 'foo') - .expect(404); - } finally { - await security.role.delete(roleName); - await security.user.delete(username); - } - }); - - it(`Admin Code user can execute clone/delete with all permission`, async () => { - const username = 'another_code_admin_user'; - const roleName = 'another_code_admin_role'; - const password = `${username}-password`; - try { - await security.role.create(roleName, { - kibana: [ - { - feature: { - // Grant all permission to Code app as an admin user. - code: ['all'], - }, - spaces: ['*'], - }, - ], - }); - - await security.user.create(username, { - password, - roles: [roleName], - full_name: 'Code admin user', - }); - - // Clone repository - await supertest - .post(`/api/code/repo`) - .auth(username, password) - .set('kbn-xsrf', 'foo') - .send({ url: 'https://github.com/elastic/code-examples_single-image.git' }) - .expect(200); - - // Delete repository - await supertest - .delete(`/api/code/repo/github.com/elastic/code-examples_single-image`) - .auth(username, password) - .set('kbn-xsrf', 'foo') - .expect(200); - } finally { - await security.role.delete(roleName); - await security.user.delete(username); - } - }); - - it(`APIs can't be accessed by .code-* read privileges role`, async () => { - const username = 'logstash_read'; - const roleName = 'logstash_read'; - const password = `${username}-password`; - try { - await security.role.create(roleName, {}); - - await security.user.create(username, { - password, - roles: [roleName], - full_name: 'a kibana user', - }); - - await executeRequests(username, password, '', 'forbidden'); - } finally { - await security.role.delete(roleName); - await security.user.delete(username); - } - }); - - it(`APIs can be accessed global all with .code-* read privileges role`, async () => { - const username = 'global_all'; - const roleName = 'global_all'; - const password = `${username}-password`; - try { - await security.role.create(roleName, { - kibana: [ - { - base: ['all'], - spaces: ['*'], - }, - ], - }); - - await security.user.create(username, { - password, - roles: [roleName], - full_name: 'a kibana user', - }); - - await executeRequests(username, password, '', 'response'); - } finally { - await security.role.delete(roleName); - await security.user.delete(username); - } - }); - - it(`APIs can't be accessed by dashboard all with .code-* read privileges role`, async () => { - const username = 'dashboard_all'; - const roleName = 'dashboard_all'; - const password = `${username}-password`; - try { - await security.role.create(roleName, { - kibana: [ - { - feature: { - dashboard: ['all'], - }, - spaces: ['*'], - }, - ], - }); - - await security.user.create(username, { - password, - roles: [roleName], - full_name: 'a kibana user', - }); - - await executeRequests(username, password, '', 'forbidden'); - } finally { - await security.role.delete(roleName); - await security.user.delete(username); - } - }); - - describe('spaces', () => { - // the following tests create a user_1 which has Code read access to space_1 and dashboard all access to space_2 - const space1Id = 'space_1'; - const space2Id = 'space_2'; - - const roleName = 'user_1'; - const username = 'user_1'; - const password = 'user_1-password'; - - before(async () => { - await spaces.create({ - id: space1Id, - name: space1Id, - disabledFeatures: [], - }); - await spaces.create({ - id: space2Id, - name: space2Id, - disabledFeatures: [], - }); - await security.role.create(roleName, { - kibana: [ - { - feature: { - code: ['read'], - }, - spaces: ['default'], - }, - { - feature: { - code: ['read'], - }, - spaces: [space1Id], - }, - { - feature: { - dashboard: ['all'], - }, - spaces: [space2Id], - }, - ], - }); - await security.user.create(username, { - password, - roles: [roleName], - }); - }); - - after(async () => { - await spaces.delete(space1Id); - await spaces.delete(space2Id); - await security.role.delete(roleName); - await security.user.delete(username); - }); - - it('user_1 can access all APIs in default space', async () => { - await executeRequests(username, password, '', 'response'); - }); - - it('user_1 can access code admin APIs in space_1', async () => { - await executeRequests(username, password, space1Id, 'response', true); - }); - - it(`user_1 cannot access APIs in space_2`, async () => { - await executeRequests(username, password, space2Id, 'forbidden'); - }); - }); - }); -} diff --git a/x-pack/test/api_integration/apis/code/index.ts b/x-pack/test/api_integration/apis/code/index.ts deleted file mode 100644 index 68f15c318a72d..0000000000000 --- a/x-pack/test/api_integration/apis/code/index.ts +++ /dev/null @@ -1,14 +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 { FtrProviderContext } from '../../ftr_provider_context'; - -export default function apmApiIntegrationTests({ loadTestFile }: FtrProviderContext) { - describe('Code', () => { - loadTestFile(require.resolve('./feature_controls')); - loadTestFile(require.resolve('./repo_status')); - }); -} diff --git a/x-pack/test/api_integration/apis/code/repo_status.ts b/x-pack/test/api_integration/apis/code/repo_status.ts deleted file mode 100644 index f730ebdf9bc66..0000000000000 --- a/x-pack/test/api_integration/apis/code/repo_status.ts +++ /dev/null @@ -1,93 +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 expect from '@kbn/expect'; -import { FtrProviderContext } from '../../ftr_provider_context'; -import { - RepoFileStatus, - StatusReport, -} from '../../../../legacy/plugins/code/common/repo_file_status'; - -export default function repoStatusTests({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const retry = getService('retry'); - const log = getService('log'); - - const CLONE_API = '/api/code/repo'; - const DELETE_API = '/api/code/repo'; - const REPO_STATUS_API = '/api/code/repo/status'; - const TEST_REPO = 'github.com/elastic/TypeScript-Node-Starter'; - const TEST_REPO_URL = `https://${TEST_REPO}.git`; - - // FLAKY: https://github.com/elastic/kibana/issues/41336 - describe.skip('repo status', () => { - after(async () => { - await supertest - .delete(`${DELETE_API}/${TEST_REPO}`) - .set('kbn-xsrf', 'xxx') - .expect(200); - }); - - async function getStatus(revision: string, path: string): Promise { - const api = `/api/code/repo/${TEST_REPO}/status/${revision}/${path}`; - const { body } = await supertest.get(api).expect(200); - return body as StatusReport; - } - - it('', async () => { - { - // send a clone request - const response = await supertest - .post(CLONE_API) - .set('kbn-xsrf', 'xxx') - .send({ url: TEST_REPO_URL }) - .expect(200); - expect(response.body).to.eql({ - uri: TEST_REPO, - url: TEST_REPO_URL, - name: 'TypeScript-Node-Starter', - org: 'elastic', - protocol: 'https', - }); - } - log.info('cloning'); - await retry.tryForTime(60000, async () => { - const { body } = await supertest.get(`${REPO_STATUS_API}/${TEST_REPO}`).expect(200); - log.info('clone progress:' + body.gitStatus.progress); - expect(body.gitStatus).to.have.property('progress', 100); - }); - log.info('indexing'); - let sawInitializing = false; - await retry.tryForTime(60000, async () => { - const { body } = await supertest.get(`${REPO_STATUS_API}/${TEST_REPO}`).expect(200); - expect(body.indexStatus).to.have.property('progress'); - if (body.indexStatus.progress === 0 && !sawInitializing) { - const status = await getStatus('master', 'src/app.ts'); - expect(status.langServerStatus).to.be(RepoFileStatus.LANG_SERVER_IS_INITIALIZING); - sawInitializing = true; - } - expect(sawInitializing).to.be(true); - // indexing - if (body.progress < 100) { - const status = getStatus('master', ''); - expect(status).to.have.property('repoStatus', RepoFileStatus.INDEXING); - } - expect(body.indexStatus.progress).to.be(100); - log.info('index done'); - const status = await getStatus('master', ''); - expect(status.repoStatus).not.to.be(RepoFileStatus.INDEXING); - }); - { - const status = await getStatus('46971a8454761f1a11d8fde4d96ff8d29bc4e754', ''); - expect(status.repoStatus).to.be(RepoFileStatus.REVISION_NOT_INDEXED); - } - { - const status = await getStatus('master', 'README.md'); - expect(status.fileStatus).to.be(RepoFileStatus.FILE_NOT_SUPPORTED); - } - }); - }); -} diff --git a/x-pack/test/api_integration/apis/features/features/features.ts b/x-pack/test/api_integration/apis/features/features/features.ts index fb17c76cb6bc8..ef0f0451ee058 100644 --- a/x-pack/test/api_integration/apis/features/features/features.ts +++ b/x-pack/test/api_integration/apis/features/features/features.ts @@ -110,7 +110,6 @@ export default function({ getService }: FtrProviderContext) { 'ml', 'apm', 'canvas', - 'code', 'infrastructure', 'logs', 'maps', diff --git a/x-pack/test/api_integration/apis/index.js b/x-pack/test/api_integration/apis/index.js index 583690ad3d287..86ef445899039 100644 --- a/x-pack/test/api_integration/apis/index.js +++ b/x-pack/test/api_integration/apis/index.js @@ -25,7 +25,6 @@ export default function ({ loadTestFile }) { loadTestFile(require.resolve('./maps')); loadTestFile(require.resolve('./apm')); loadTestFile(require.resolve('./siem')); - loadTestFile(require.resolve('./code')); loadTestFile(require.resolve('./short_urls')); loadTestFile(require.resolve('./lens')); }); diff --git a/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/ccr_shard.json b/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/ccr_shard.json index a8aab6bddc1e7..abdf105c02c4b 100644 --- a/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/ccr_shard.json +++ b/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/ccr_shard.json @@ -2,7 +2,7 @@ "formattedLeader": "leader", "metrics": { "ccr_sync_lag_time": [{ - "bucket_size": "10 min", + "bucket_size": "10 mins", "timeRange": { "min": 1537315200000, "max": 1537401599000 @@ -22,7 +22,7 @@ "data": [] }], "ccr_sync_lag_ops": [{ - "bucket_size": "10 min", + "bucket_size": "10 mins", "timeRange": { "min": 1537315200000, "max": 1537401599000 diff --git a/x-pack/test/api_integration/apis/security/privileges.ts b/x-pack/test/api_integration/apis/security/privileges.ts index 846d5cbdf4e1a..d4c8a3e68c50e 100644 --- a/x-pack/test/api_integration/apis/security/privileges.ts +++ b/x-pack/test/api_integration/apis/security/privileges.ts @@ -37,7 +37,6 @@ export default function({ getService }: FtrProviderContext) { uptime: ['all', 'read'], apm: ['all', 'read'], siem: ['all', 'read'], - code: ['all', 'read'], }, global: ['all', 'read'], space: ['all', 'read'], diff --git a/x-pack/test/api_integration/apis/siem/events_over_time.ts b/x-pack/test/api_integration/apis/siem/events_over_time.ts index 253dbbab77c3c..10b81734b7b79 100644 --- a/x-pack/test/api_integration/apis/siem/events_over_time.ts +++ b/x-pack/test/api_integration/apis/siem/events_over_time.ts @@ -29,7 +29,6 @@ export default function({ getService }: FtrProviderContext) { interval: '12h', to: TO, from: FROM, - timezone: 'America/Denver', }, defaultIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], inspect: false, @@ -70,7 +69,6 @@ export default function({ getService }: FtrProviderContext) { interval: '12h', to: TO, from: FROM, - timezone: 'America/Denver', }, defaultIndex: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], inspect: false, diff --git a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_charts.json b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_charts.json index f5368ad7ecf0c..dbfc17a468796 100644 --- a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_charts.json +++ b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_charts.json @@ -103,6 +103,10 @@ { "x": 1568173204510, "y": null + }, + { + "x": 1568173227311, + "y": 24627 } ] } @@ -257,6 +261,12 @@ "up": null, "down": null, "total": 0 + }, + { + "x": 1568173227311, + "up": null, + "down": null, + "total": 1 } ], "statusMaxCount": 0, diff --git a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/snapshot_histogram.json b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/snapshot_histogram.json index c12ec7f3847c3..cf88ccae9cb99 100644 --- a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/snapshot_histogram.json +++ b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/snapshot_histogram.json @@ -1,179 +1,188 @@ { - "histogram": [ - { - "upCount": 93, - "downCount": 7, - "x": 1568172680087, - "x0": 1568172657286, - "y": 1 - }, - { - "upCount": 93, - "downCount": 7, - "x": 1568172702888, - "x0": 1568172680087, - "y": 1 - }, - { - "upCount": 93, - "downCount": 7, - "x": 1568172725689, - "x0": 1568172702888, - "y": 1 - }, - { - "upCount": 0, - "downCount": 0, - "x": 1568172748490, - "x0": 1568172725689, - "y": 1 - }, - { - "upCount": 93, - "downCount": 7, - "x": 1568172771291, - "x0": 1568172748490, - "y": 1 - }, - { - "upCount": 93, - "downCount": 7, - "x": 1568172794092, - "x0": 1568172771291, - "y": 1 - }, - { - "upCount": 92, - "downCount": 8, - "x": 1568172816893, - "x0": 1568172794092, - "y": 1 - }, - { - "upCount": 0, - "downCount": 0, - "x": 1568172839694, - "x0": 1568172816893, - "y": 1 - }, - { - "upCount": 93, - "downCount": 7, - "x": 1568172862495, - "x0": 1568172839694, - "y": 1 - }, - { - "upCount": 93, - "downCount": 7, - "x": 1568172885296, - "x0": 1568172862495, - "y": 1 - }, - { - "upCount": 93, - "downCount": 7, - "x": 1568172908097, - "x0": 1568172885296, - "y": 1 - }, - { - "upCount": 0, - "downCount": 0, - "x": 1568172930898, - "x0": 1568172908097, - "y": 1 - }, - { - "upCount": 93, - "downCount": 7, - "x": 1568172953699, - "x0": 1568172930898, - "y": 1 - }, - { - "upCount": 93, - "downCount": 7, - "x": 1568172976500, - "x0": 1568172953699, - "y": 1 - }, - { - "upCount": 92, - "downCount": 8, - "x": 1568172999301, - "x0": 1568172976500, - "y": 1 - }, - { - "upCount": 0, - "downCount": 0, - "x": 1568173022102, - "x0": 1568172999301, - "y": 1 - }, - { - "upCount": 93, - "downCount": 7, - "x": 1568173044903, - "x0": 1568173022102, - "y": 1 - }, - { - "upCount": 93, - "downCount": 7, - "x": 1568173067704, - "x0": 1568173044903, - "y": 1 - }, - { - "upCount": 93, - "downCount": 7, - "x": 1568173090505, - "x0": 1568173067704, - "y": 1 - }, - { - "upCount": 0, - "downCount": 0, - "x": 1568173113306, - "x0": 1568173090505, - "y": 1 - }, - { - "upCount": 93, - "downCount": 7, - "x": 1568173136107, - "x0": 1568173113306, - "y": 1 - }, - { - "upCount": 93, - "downCount": 7, - "x": 1568173158908, - "x0": 1568173136107, - "y": 1 - }, - { - "upCount": 92, - "downCount": 8, - "x": 1568173181709, - "x0": 1568173158908, - "y": 1 - }, - { - "upCount": 93, - "downCount": 7, - "x": 1568173204510, - "x0": 1568173181709, - "y": 1 - }, - { - "upCount": 0, - "downCount": 0, - "x": 1568173227311, - "x0": 1568173204510, - "y": 1 - } - ] + "queryResult": { + "histogram": [ + { + "upCount": 93, + "downCount": 7, + "x": 1568172657286, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 7, + "x": 1568172680087, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 7, + "x": 1568172702888, + "x0": null, + "y": 1 + }, + { + "upCount": 0, + "downCount": 0, + "x": 1568172725689, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 7, + "x": 1568172748490, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 7, + "x": 1568172771291, + "x0": null, + "y": 1 + }, + { + "upCount": 92, + "downCount": 8, + "x": 1568172794092, + "x0": null, + "y": 1 + }, + { + "upCount": 0, + "downCount": 0, + "x": 1568172816893, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 7, + "x": 1568172839694, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 7, + "x": 1568172862495, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 7, + "x": 1568172885296, + "x0": null, + "y": 1 + }, + { + "upCount": 0, + "downCount": 0, + "x": 1568172908097, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 7, + "x": 1568172930898, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 7, + "x": 1568172953699, + "x0": null, + "y": 1 + }, + { + "upCount": 92, + "downCount": 8, + "x": 1568172976500, + "x0": null, + "y": 1 + }, + { + "upCount": 0, + "downCount": 0, + "x": 1568172999301, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 7, + "x": 1568173022102, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 7, + "x": 1568173044903, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 7, + "x": 1568173067704, + "x0": null, + "y": 1 + }, + { + "upCount": 0, + "downCount": 0, + "x": 1568173090505, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 7, + "x": 1568173113306, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 7, + "x": 1568173136107, + "x0": null, + "y": 1 + }, + { + "upCount": 92, + "downCount": 8, + "x": 1568173158908, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 7, + "x": 1568173181709, + "x0": null, + "y": 1 + }, + { + "upCount": 0, + "downCount": 0, + "x": 1568173204510, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 7, + "x": 1568173227311, + "x0": null, + "y": 1 + } + ] + } } \ No newline at end of file diff --git a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/snapshot_histogram_by_filter.json b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/snapshot_histogram_by_filter.json index f61a101ce4462..383d4acd96340 100644 --- a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/snapshot_histogram_by_filter.json +++ b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/snapshot_histogram_by_filter.json @@ -1,179 +1,188 @@ { - "histogram": [ - { - "upCount": 93, - "downCount": 0, - "x": 1568172680087, - "x0": 1568172657286, - "y": 1 - }, - { - "upCount": 93, - "downCount": 0, - "x": 1568172702888, - "x0": 1568172680087, - "y": 1 - }, - { - "upCount": 93, - "downCount": 0, - "x": 1568172725689, - "x0": 1568172702888, - "y": 1 - }, - { - "upCount": 0, - "downCount": 0, - "x": 1568172748490, - "x0": 1568172725689, - "y": 1 - }, - { - "upCount": 93, - "downCount": 0, - "x": 1568172771291, - "x0": 1568172748490, - "y": 1 - }, - { - "upCount": 93, - "downCount": 0, - "x": 1568172794092, - "x0": 1568172771291, - "y": 1 - }, - { - "upCount": 92, - "downCount": 0, - "x": 1568172816893, - "x0": 1568172794092, - "y": 1 - }, - { - "upCount": 0, - "downCount": 0, - "x": 1568172839694, - "x0": 1568172816893, - "y": 1 - }, - { - "upCount": 93, - "downCount": 0, - "x": 1568172862495, - "x0": 1568172839694, - "y": 1 - }, - { - "upCount": 93, - "downCount": 0, - "x": 1568172885296, - "x0": 1568172862495, - "y": 1 - }, - { - "upCount": 93, - "downCount": 0, - "x": 1568172908097, - "x0": 1568172885296, - "y": 1 - }, - { - "upCount": 0, - "downCount": 0, - "x": 1568172930898, - "x0": 1568172908097, - "y": 1 - }, - { - "upCount": 93, - "downCount": 0, - "x": 1568172953699, - "x0": 1568172930898, - "y": 1 - }, - { - "upCount": 93, - "downCount": 0, - "x": 1568172976500, - "x0": 1568172953699, - "y": 1 - }, - { - "upCount": 92, - "downCount": 0, - "x": 1568172999301, - "x0": 1568172976500, - "y": 1 - }, - { - "upCount": 0, - "downCount": 0, - "x": 1568173022102, - "x0": 1568172999301, - "y": 1 - }, - { - "upCount": 93, - "downCount": 0, - "x": 1568173044903, - "x0": 1568173022102, - "y": 1 - }, - { - "upCount": 93, - "downCount": 0, - "x": 1568173067704, - "x0": 1568173044903, - "y": 1 - }, - { - "upCount": 93, - "downCount": 0, - "x": 1568173090505, - "x0": 1568173067704, - "y": 1 - }, - { - "upCount": 0, - "downCount": 0, - "x": 1568173113306, - "x0": 1568173090505, - "y": 1 - }, - { - "upCount": 93, - "downCount": 0, - "x": 1568173136107, - "x0": 1568173113306, - "y": 1 - }, - { - "upCount": 93, - "downCount": 0, - "x": 1568173158908, - "x0": 1568173136107, - "y": 1 - }, - { - "upCount": 92, - "downCount": 0, - "x": 1568173181709, - "x0": 1568173158908, - "y": 1 - }, - { - "upCount": 93, - "downCount": 0, - "x": 1568173204510, - "x0": 1568173181709, - "y": 1 - }, - { - "upCount": 0, - "downCount": 0, - "x": 1568173227311, - "x0": 1568173204510, - "y": 1 - } - ] + "queryResult": { + "histogram": [ + { + "upCount": 93, + "downCount": 0, + "x": 1568172657286, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 0, + "x": 1568172680087, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 0, + "x": 1568172702888, + "x0": null, + "y": 1 + }, + { + "upCount": 0, + "downCount": 0, + "x": 1568172725689, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 0, + "x": 1568172748490, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 0, + "x": 1568172771291, + "x0": null, + "y": 1 + }, + { + "upCount": 92, + "downCount": 0, + "x": 1568172794092, + "x0": null, + "y": 1 + }, + { + "upCount": 0, + "downCount": 0, + "x": 1568172816893, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 0, + "x": 1568172839694, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 0, + "x": 1568172862495, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 0, + "x": 1568172885296, + "x0": null, + "y": 1 + }, + { + "upCount": 0, + "downCount": 0, + "x": 1568172908097, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 0, + "x": 1568172930898, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 0, + "x": 1568172953699, + "x0": null, + "y": 1 + }, + { + "upCount": 92, + "downCount": 0, + "x": 1568172976500, + "x0": null, + "y": 1 + }, + { + "upCount": 0, + "downCount": 0, + "x": 1568172999301, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 0, + "x": 1568173022102, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 0, + "x": 1568173044903, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 0, + "x": 1568173067704, + "x0": null, + "y": 1 + }, + { + "upCount": 0, + "downCount": 0, + "x": 1568173090505, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 0, + "x": 1568173113306, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 0, + "x": 1568173136107, + "x0": null, + "y": 1 + }, + { + "upCount": 92, + "downCount": 0, + "x": 1568173158908, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 0, + "x": 1568173181709, + "x0": null, + "y": 1 + }, + { + "upCount": 0, + "downCount": 0, + "x": 1568173204510, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 0, + "x": 1568173227311, + "x0": null, + "y": 1 + } + ] + } } \ No newline at end of file diff --git a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/snapshot_histogram_by_id.json b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/snapshot_histogram_by_id.json index c12ec7f3847c3..cf88ccae9cb99 100644 --- a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/snapshot_histogram_by_id.json +++ b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/snapshot_histogram_by_id.json @@ -1,179 +1,188 @@ { - "histogram": [ - { - "upCount": 93, - "downCount": 7, - "x": 1568172680087, - "x0": 1568172657286, - "y": 1 - }, - { - "upCount": 93, - "downCount": 7, - "x": 1568172702888, - "x0": 1568172680087, - "y": 1 - }, - { - "upCount": 93, - "downCount": 7, - "x": 1568172725689, - "x0": 1568172702888, - "y": 1 - }, - { - "upCount": 0, - "downCount": 0, - "x": 1568172748490, - "x0": 1568172725689, - "y": 1 - }, - { - "upCount": 93, - "downCount": 7, - "x": 1568172771291, - "x0": 1568172748490, - "y": 1 - }, - { - "upCount": 93, - "downCount": 7, - "x": 1568172794092, - "x0": 1568172771291, - "y": 1 - }, - { - "upCount": 92, - "downCount": 8, - "x": 1568172816893, - "x0": 1568172794092, - "y": 1 - }, - { - "upCount": 0, - "downCount": 0, - "x": 1568172839694, - "x0": 1568172816893, - "y": 1 - }, - { - "upCount": 93, - "downCount": 7, - "x": 1568172862495, - "x0": 1568172839694, - "y": 1 - }, - { - "upCount": 93, - "downCount": 7, - "x": 1568172885296, - "x0": 1568172862495, - "y": 1 - }, - { - "upCount": 93, - "downCount": 7, - "x": 1568172908097, - "x0": 1568172885296, - "y": 1 - }, - { - "upCount": 0, - "downCount": 0, - "x": 1568172930898, - "x0": 1568172908097, - "y": 1 - }, - { - "upCount": 93, - "downCount": 7, - "x": 1568172953699, - "x0": 1568172930898, - "y": 1 - }, - { - "upCount": 93, - "downCount": 7, - "x": 1568172976500, - "x0": 1568172953699, - "y": 1 - }, - { - "upCount": 92, - "downCount": 8, - "x": 1568172999301, - "x0": 1568172976500, - "y": 1 - }, - { - "upCount": 0, - "downCount": 0, - "x": 1568173022102, - "x0": 1568172999301, - "y": 1 - }, - { - "upCount": 93, - "downCount": 7, - "x": 1568173044903, - "x0": 1568173022102, - "y": 1 - }, - { - "upCount": 93, - "downCount": 7, - "x": 1568173067704, - "x0": 1568173044903, - "y": 1 - }, - { - "upCount": 93, - "downCount": 7, - "x": 1568173090505, - "x0": 1568173067704, - "y": 1 - }, - { - "upCount": 0, - "downCount": 0, - "x": 1568173113306, - "x0": 1568173090505, - "y": 1 - }, - { - "upCount": 93, - "downCount": 7, - "x": 1568173136107, - "x0": 1568173113306, - "y": 1 - }, - { - "upCount": 93, - "downCount": 7, - "x": 1568173158908, - "x0": 1568173136107, - "y": 1 - }, - { - "upCount": 92, - "downCount": 8, - "x": 1568173181709, - "x0": 1568173158908, - "y": 1 - }, - { - "upCount": 93, - "downCount": 7, - "x": 1568173204510, - "x0": 1568173181709, - "y": 1 - }, - { - "upCount": 0, - "downCount": 0, - "x": 1568173227311, - "x0": 1568173204510, - "y": 1 - } - ] + "queryResult": { + "histogram": [ + { + "upCount": 93, + "downCount": 7, + "x": 1568172657286, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 7, + "x": 1568172680087, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 7, + "x": 1568172702888, + "x0": null, + "y": 1 + }, + { + "upCount": 0, + "downCount": 0, + "x": 1568172725689, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 7, + "x": 1568172748490, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 7, + "x": 1568172771291, + "x0": null, + "y": 1 + }, + { + "upCount": 92, + "downCount": 8, + "x": 1568172794092, + "x0": null, + "y": 1 + }, + { + "upCount": 0, + "downCount": 0, + "x": 1568172816893, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 7, + "x": 1568172839694, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 7, + "x": 1568172862495, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 7, + "x": 1568172885296, + "x0": null, + "y": 1 + }, + { + "upCount": 0, + "downCount": 0, + "x": 1568172908097, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 7, + "x": 1568172930898, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 7, + "x": 1568172953699, + "x0": null, + "y": 1 + }, + { + "upCount": 92, + "downCount": 8, + "x": 1568172976500, + "x0": null, + "y": 1 + }, + { + "upCount": 0, + "downCount": 0, + "x": 1568172999301, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 7, + "x": 1568173022102, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 7, + "x": 1568173044903, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 7, + "x": 1568173067704, + "x0": null, + "y": 1 + }, + { + "upCount": 0, + "downCount": 0, + "x": 1568173090505, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 7, + "x": 1568173113306, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 7, + "x": 1568173136107, + "x0": null, + "y": 1 + }, + { + "upCount": 92, + "downCount": 8, + "x": 1568173158908, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 7, + "x": 1568173181709, + "x0": null, + "y": 1 + }, + { + "upCount": 0, + "downCount": 0, + "x": 1568173204510, + "x0": null, + "y": 1 + }, + { + "upCount": 93, + "downCount": 7, + "x": 1568173227311, + "x0": null, + "y": 1 + } + ] + } } \ No newline at end of file diff --git a/x-pack/test/api_integration/apis/uptime/graphql/snapshot_histogram.ts b/x-pack/test/api_integration/apis/uptime/graphql/snapshot_histogram.ts index 7af9de99d8327..02fd3fd630d4b 100644 --- a/x-pack/test/api_integration/apis/uptime/graphql/snapshot_histogram.ts +++ b/x-pack/test/api_integration/apis/uptime/graphql/snapshot_histogram.ts @@ -7,6 +7,7 @@ import { snapshotHistogramQueryString } from '../../../../../legacy/plugins/uptime/public/queries/snapshot_histogram_query'; import { expectFixtureEql } from './helpers/expect_fixture_eql'; import { FtrProviderContext } from '../../../ftr_provider_context'; +import { assertCloseTo } from '../../../../../legacy/plugins/uptime/server/lib/helper'; export default function({ getService }: FtrProviderContext) { describe('snapshotHistogram', () => { @@ -31,6 +32,10 @@ export default function({ getService }: FtrProviderContext) { .post('/api/uptime/graphql') .set('kbn-xsrf', 'foo') .send({ ...getSnapshotHistogramQuery }); + // manually testing this value and then removing it to avoid flakiness + const { interval } = data.queryResult; + assertCloseTo(interval, 22801, 100); + delete data.queryResult.interval; expectFixtureEql(data, 'snapshot_histogram'); }); @@ -50,6 +55,9 @@ export default function({ getService }: FtrProviderContext) { .post('/api/uptime/graphql') .set('kbn-xsrf', 'foo') .send({ ...getSnapshotHistogramQuery }); + const { interval } = data.queryResult; + assertCloseTo(interval, 22801, 100); + delete data.queryResult.interval; expectFixtureEql(data, 'snapshot_histogram_by_id'); }); @@ -71,6 +79,9 @@ export default function({ getService }: FtrProviderContext) { .post('/api/uptime/graphql') .set('kbn-xsrf', 'foo') .send({ ...getSnapshotHistogramQuery }); + const { interval } = data.queryResult; + assertCloseTo(interval, 22801, 100); + delete data.queryResult.interval; expectFixtureEql(data, 'snapshot_histogram_by_filter'); }); }); diff --git a/x-pack/test/functional/apps/code/code_intelligence.ts b/x-pack/test/functional/apps/code/code_intelligence.ts deleted file mode 100644 index 012a349d6a882..0000000000000 --- a/x-pack/test/functional/apps/code/code_intelligence.ts +++ /dev/null @@ -1,230 +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 { REPO_ROOT } from '@kbn/dev-utils'; -import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../ftr_provider_context'; -import { load as repoLoad, unload as repoUnload } from './repo_archiver'; - -export default function codeIntelligenceFunctionalTests({ - getService, - getPageObjects, -}: FtrProviderContext) { - const esArchiver = getService('esArchiver'); - const testSubjects = getService('testSubjects'); - const retry = getService('retry'); - const log = getService('log'); - const browser = getService('browser'); - const find = getService('find'); - const config = getService('config'); - const FIND_TIME = config.get('timeouts.find'); - const PageObjects = getPageObjects(['common', 'header', 'security', 'code', 'home']); - - const exists = async (selector: string) => - await testSubjects.exists(selector, { allowHidden: true }); - - // FLAKY: https://github.com/elastic/kibana/issues/45094 - describe.skip('Code Intelligence', () => { - describe('Code intelligence in source view page', () => { - const repositoryListSelector = 'codeRepositoryList > codeRepositoryItem'; - const testGoToDefinition = async () => { - await retry.try(async () => { - expect(await exists('codeSourceViewer')).to.be(true); - }); - - // Hover on the 'UserModel' reference on line 5. - await retry.tryForTime(300000, async () => { - const spans = await find.allByCssSelector('.mtk32', FIND_TIME); - expect(spans.length).to.greaterThan(1); - const userModelSpan = spans[1]; - expect(await userModelSpan.getVisibleText()).to.equal('UserModel'); - await userModelSpan.moveMouseTo(); - // Expect the go to definition button show up eventually. - expect(await exists('codeGoToDefinitionButton')).to.be(true); - - await testSubjects.click('codeGoToDefinitionButton'); - await retry.tryForTime(5000, async () => { - const currentUrl: string = await browser.getCurrentUrl(); - log.info(`Jump to url: ${currentUrl}`); - // Expect to jump to src/models/User.ts file on line 5. - expect(currentUrl.indexOf('src/models/User.ts!L5:13')).to.greaterThan(0); - }); - }); - }; - const testGoToDefinitionFromRoot = async () => { - log.debug('Hover on a reference and jump to definition across file'); - - // Visit the /src/controllers/user.ts file - // Wait the file tree to be rendered and click the 'src' folder on the file tree. - await retry.try(async () => { - expect(await exists('codeFileTreeNode-Directory-src')).to.be(true); - }); - - await testSubjects.click('codeFileTreeNode-Directory-src'); - - await retry.try(async () => { - expect(await exists('codeFileTreeNode-Directory-src/controllers')).to.be(true); - }); - - await testSubjects.click('codeFileTreeNode-Directory-src/controllers'); - // Then the 'controllers' folder on the file tree. - await retry.try(async () => { - expect(await exists('codeFileTreeNode-File-src/controllers/user.ts')).to.be(true); - }); - - await testSubjects.click('codeFileTreeNode-File-src/controllers/user.ts'); - // Then the 'user.ts' file on the file tree. - await testGoToDefinition(); - }; - - before(async () => { - await repoLoad( - 'github.com/elastic/TypeScript-Node-Starter', - 'typescript_node_starter', - config.get('kbnTestServer.installDir') || REPO_ROOT - ); - await esArchiver.load('code/repositories/typescript_node_starter'); - }); - - after(async () => { - await PageObjects.security.forceLogout(); - await esArchiver.unload('code/repositories/typescript_node_starter'); - await repoUnload( - 'github.com/elastic/TypeScript-Node-Starter', - config.get('kbnTestServer.installDir') || REPO_ROOT - ); - }); - - beforeEach(async () => { - // Navigate to the code app. - await PageObjects.common.navigateToApp('code'); - await PageObjects.header.waitUntilLoadingHasFinished(); - - // Enter the first repository from the admin page. - await testSubjects.click(repositoryListSelector); - }); - - it('Hover on a reference and jump to definition across file', async () => { - await testGoToDefinitionFromRoot(); - - // can go back and go to definition again - await browser.goBack(); - await testGoToDefinition(); - }); - - it('Find references and jump to reference', async () => { - log.debug('Find references and jump to reference'); - - // Visit the /src/models/User.ts file - // Wait the file tree to be rendered and click the 'src' folder on the file tree. - await retry.try(async () => { - expect(await exists('codeFileTreeNode-Directory-src')).to.be(true); - }); - - await testSubjects.click('codeFileTreeNode-Directory-src'); - await retry.try(async () => { - expect(await exists('codeFileTreeNode-Directory-src/models')).to.be(true); - }); - - await testSubjects.click('codeFileTreeNode-Directory-src/models'); - // Then the 'models' folder on the file tree. - await retry.try(async () => { - expect(await exists('codeFileTreeNode-File-src/models/User.ts')).to.be(true); - }); - - await testSubjects.click('codeFileTreeNode-File-src/models/User.ts'); - // Then the 'User.ts' file on the file tree. - await retry.try(async () => { - expect(await exists('codeSourceViewer')).to.be(true); - }); - - // Hover on the 'UserModel' reference on line 5. - await retry.tryForTime(300000, async () => { - const spans = await find.allByCssSelector('.mtk32', FIND_TIME); - expect(spans.length).to.greaterThan(1); - const userModelSpan = spans[0]; - expect(await userModelSpan.getVisibleText()).to.equal('UserModel'); - await userModelSpan.moveMouseTo(); - // Expect the go to definition button show up eventually. - expect(await exists('codeFindReferenceButton')).to.be(true); - - await testSubjects.click('codeFindReferenceButton'); - await retry.tryForTime(5000, async () => { - // Expect the find references panel show up and having highlights. - const highlightSpans = await find.allByCssSelector('.codeSearch__highlight', FIND_TIME); - expect(highlightSpans.length).to.greaterThan(0); - const firstReference = highlightSpans[0]; - await firstReference.click(); - const currentUrl: string = await browser.getCurrentUrl(); - log.info(`Jump to url: ${currentUrl}`); - // Expect to jump to src/controllers/user.ts file on line 42. - expect(currentUrl.indexOf('src/controllers/user.ts!L42:0')).to.greaterThan(0); - }); - }); - }); - - it('Hover on a reference and jump to a different repository', async () => { - log.debug('Hover on a reference and jump to a different repository'); - - // Visit the /src/controllers/user.ts file - // Wait the file tree to be rendered and click the 'src' folder on the file tree. - await retry.try(async () => { - expect(await exists('codeFileTreeNode-Directory-src')).to.be(true); - }); - - await testSubjects.click('codeFileTreeNode-Directory-src'); - await retry.try(async () => { - expect(await exists('codeFileTreeNode-Directory-src/controllers')).to.be(true); - }); - - await testSubjects.click('codeFileTreeNode-Directory-src/controllers'); - // Then the 'controllers' folder on the file tree. - await retry.try(async () => { - expect(await exists('codeFileTreeNode-File-src/controllers/user.ts')).to.be(true); - }); - - await testSubjects.click('codeFileTreeNode-File-src/controllers/user.ts'); - // Then the 'user.ts' file on the file tree. - await retry.try(async () => { - expect(await exists('codeSourceViewer')).to.be(true); - }); - - // Hover on the 'async' reference on line 1. - await retry.tryForTime(300000, async () => { - const spans = await find.allByCssSelector('.mtk9', FIND_TIME); - expect(spans.length).to.greaterThan(1); - const asyncSpan = spans[1]; - expect(await asyncSpan.getVisibleText()).to.equal('async'); - await asyncSpan.moveMouseTo(); - // Expect the go to definition button show up eventually. - expect(await exists('codeGoToDefinitionButton')).to.be(true); - - await testSubjects.click('codeGoToDefinitionButton'); - // TODO: figure out why jenkins will fail the following test while locally it - // passes. - // await retry.tryForTime(5000, async () => { - // const currentUrl: string = await browser.getCurrentUrl(); - // log.error(`Jump to url: ${currentUrl}`); - // // Expect to jump to repository github.com/DefinitelyTyped/DefinitelyTyped. - // expect(currentUrl.indexOf('github.com/DefinitelyTyped/DefinitelyTyped')).to.greaterThan( - // 0 - // ); - // }); - - // it should goes back to controllers/user.ts - // await browser.goBack(); - - // await retry.try(async () => { - // const $spans = await find.allByCssSelector('.mtk32', FIND_TIME); - // expect($spans.length).to.greaterThan(1); - // const $userModelSpan = $spans[1]; - // expect(await $userModelSpan.getVisibleText()).to.equal('UserModel'); - // }); - }); - }); - }); - }); -} diff --git a/x-pack/test/functional/apps/code/explore_repository.ts b/x-pack/test/functional/apps/code/explore_repository.ts deleted file mode 100644 index 3103e56ab6a90..0000000000000 --- a/x-pack/test/functional/apps/code/explore_repository.ts +++ /dev/null @@ -1,337 +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 { REPO_ROOT } from '@kbn/dev-utils'; -import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../ftr_provider_context'; -import { load as repoLoad, unload as repoUnload } from './repo_archiver'; - -export default function exploreRepositoryFunctionalTests({ - getService, - getPageObjects, -}: FtrProviderContext) { - const esArchiver = getService('esArchiver'); - const browser = getService('browser'); - const testSubjects = getService('testSubjects'); - const retry = getService('retry'); - const log = getService('log'); - const find = getService('find'); - const config = getService('config'); - const PageObjects = getPageObjects(['common', 'header', 'security', 'code', 'home']); - - const exists = async (selector: string) => - await testSubjects.exists(selector, { allowHidden: true }); - - const FIND_TIME = config.get('timeouts.find'); - - // FLAKY: https://github.com/elastic/kibana/issues/41338 - describe.skip('Explore Repository', function() { - this.tags('smoke'); - describe('Explore a repository', () => { - const repositoryListSelector = 'codeRepositoryList > codeRepositoryItem'; - - before(async () => { - await repoLoad( - 'github.com/elastic/TypeScript-Node-Starter', - 'typescript_node_starter', - config.get('kbnTestServer.installDir') || REPO_ROOT - ); - await esArchiver.load('code/repositories/typescript_node_starter'); - }); - - after(async () => { - await PageObjects.security.forceLogout(); - await esArchiver.unload('code/repositories/typescript_node_starter'); - await repoUnload( - 'github.com/elastic/TypeScript-Node-Starter', - config.get('kbnTestServer.installDir') || REPO_ROOT - ); - }); - - beforeEach(async () => { - // Navigate to the code app. - await PageObjects.common.navigateToApp('code'); - await PageObjects.header.waitUntilLoadingHasFinished(); - - // Enter the first repository from the admin page. - await testSubjects.click(repositoryListSelector); - }); - - afterEach(async () => { - // Navigate to the code app. - await PageObjects.common.navigateToApp('code'); - await PageObjects.header.waitUntilLoadingHasFinished(); - - // Enter the first repository from the admin page. - await testSubjects.click(repositoryListSelector); - }); - - it('open a file that does not exists should load tree', async () => { - // open a file that does not exists - const url = `${PageObjects.common.getHostPort()}/app/code#/github.com/elastic/TypeScript-Node-Starter/tree/master/I_DO_NOT_EXIST`; - await browser.get(url); - await PageObjects.header.awaitKibanaChrome(); - - await retry.try(async () => { - const currentUrl: string = await browser.getCurrentUrl(); - expect( - currentUrl.indexOf( - 'github.com/elastic/TypeScript-Node-Starter/tree/master/I_DO_NOT_EXIST' - ) - ).to.greaterThan(0); - }); - await retry.try(async () => { - expect(await exists('codeFileTreeNode-Directory-src')).ok(); - expect(await exists('codeFileTreeNode-Directory-src-doc')).ok(); - expect(await exists('codeFileTreeNode-Directory-test')).ok(); - expect(await exists('codeFileTreeNode-Directory-views')).ok(); - expect(await exists('codeFileTreeNode-File-package.json')).ok(); - }); - }); - - it('tree should be loaded', async () => { - await retry.tryForTime(5000, async () => { - expect(await exists('codeFileTreeNode-Directory-src')).ok(); - expect(await exists('codeFileTreeNode-Directory-src-doc')).ok(); - expect(await exists('codeFileTreeNode-Directory-test')).ok(); - expect(await exists('codeFileTreeNode-Directory-views')).ok(); - expect(await exists('codeFileTreeNode-File-package.json')).ok(); - }); - }); - - it('Click file/directory on the file tree', async () => { - log.debug('Click a file in the source tree'); - // Wait the file tree to be rendered and click the 'src' folder on the file tree. - await retry.try(async () => { - expect(await exists('codeFileTreeNode-Directory-src')).to.be(true); - }); - - await testSubjects.click('codeFileTreeNode-Directory-src'); - - await retry.tryForTime(1000, async () => { - // should only open one folder at this time - expect(await exists('codeFileTreeNode-Directory-Icon-src-open')).ok(); - expect(await exists('codeFileTreeNode-Directory-Icon-src-doc-closed')).ok(); - expect(await exists('codeFileTreeNode-Directory-Icon-test-closed')).ok(); - expect(await exists('codeFileTreeNode-Directory-Icon-views-closed')).ok(); - }); - log.info('src folder opened'); - await retry.try(async () => { - expect(await exists('codeFileTreeNode-Directory-src/models')).to.be(true); - }); - - await testSubjects.click('codeFileTreeNode-Directory-src/models'); - // Then the 'models' folder on the file tree. - await retry.try(async () => { - expect(await exists('codeFileTreeNode-File-src/models/User.ts')).to.be(true); - }); - - await testSubjects.click('codeFileTreeNode-File-src/models/User.ts'); - // Then the 'User.ts' file on the file tree. - await retry.try(async () => { - expect(await exists('codeSourceViewer')).to.be(true); - }); - - // Click breadcrumb does not affect file tree - await retry.try(async () => { - expect(await exists('codeFileBreadcrumb-src')).ok(); - }); - await testSubjects.click('codeFileBreadcrumb-src'); - await retry.try(async () => { - expect(await exists('codeFileTreeNode-Directory-Icon-src-open')).ok(); - expect(await exists('codeFileTreeNode-Directory-Icon-src-doc-closed')).ok(); - expect(await exists('codeFileTreeNode-Directory-Icon-test-closed')).ok(); - expect(await exists('codeFileTreeNode-Directory-Icon-views-closed')).ok(); - }); - - // open another folder - await testSubjects.click('codeFileTreeNode-Directory-src-doc'); - await retry.tryForTime(5000, async () => { - // now we should opened two folders - expect(await exists('codeFileTreeNode-Directory-Icon-src-open')).ok(); - expect(await exists('codeFileTreeNode-Directory-Icon-src-doc-open')).ok(); - }); - - // click src again to focus on this folder and close this folder. - await testSubjects.click('codeFileTreeNode-Directory-src'); - - await retry.tryForTime(5000, async () => { - // should only close src folder - expect(await exists('codeFileTreeNode-Directory-Icon-src-closed')).ok(); - expect(await exists('codeFileTreeNode-Directory-Icon-src-doc-open')).ok(); - expect(await exists('codeFileTreeNode-Directory-Icon-test-closed')).ok(); - expect(await exists('codeFileTreeNode-Directory-Icon-views-closed')).ok(); - }); - log.info('src folder closed'); - }); - - it.skip('highlight only one symbol', async () => { - await retry.try(async () => { - expect(await exists('codeFileTreeNode-Directory-src')).ok(); - }); - await testSubjects.click('codeFileTreeNode-Directory-src'); - await retry.try(async () => { - expect(await exists('codeFileTreeNode-Directory-src/controllers')).ok(); - }); - await testSubjects.click('codeFileTreeNode-Directory-src/controllers'); - await retry.try(async () => { - expect(await exists('codeFileTreeNode-File-src/controllers/user.ts')).ok(); - }); - await testSubjects.click('codeFileTreeNode-File-src/controllers/user.ts'); - await retry.try(async () => { - expect(await exists('codeStructureTreeTab')).ok(); - }); - - await retry.try(async () => { - // Retry click the structure tab in case it's not ready yet - await testSubjects.click('codeStructureTreeTab'); - expect(await exists('codeStructureTreeNode-errors')).ok(); - }); - await testSubjects.click('codeStructureTreeNode-errors'); - - await retry.try(async () => { - const highlightSymbols = await find.allByCssSelector('.code-full-width-node', FIND_TIME); - expect(highlightSymbols).to.have.length(1); - }); - }); - - it('click a breadcrumb should not affect the file tree', async () => { - log.debug('it goes to a deep node of file tree'); - const url = `${PageObjects.common.getHostPort()}/app/code#/github.com/elastic/TypeScript-Node-Starter/blob/master/src/models/User.ts`; - await browser.get(url); - await PageObjects.header.awaitKibanaChrome(); - - // Click breadcrumb does not affect file tree - await retry.try(async () => { - expect(await exists('codeFileBreadcrumb-src')).ok(); - }); - await testSubjects.click('codeFileBreadcrumb-src'); - await retry.try(async () => { - expect(await exists('codeFileTreeNode-Directory-Icon-src-open')).ok(); - expect(await exists('codeFileTreeNode-Directory-Icon-src-doc-closed')).ok(); - expect(await exists('codeFileTreeNode-Directory-Icon-test-closed')).ok(); - expect(await exists('codeFileTreeNode-Directory-Icon-views-closed')).ok(); - }); - }); - - it('Click file/directory on the right panel', async () => { - log.debug('Click file/directory on the right panel'); - - // Wait the file tree to be rendered and click the 'src' folder on the file tree. - await retry.try(async () => { - expect(await exists('codeFileExplorerNode-src')).to.be(true); - }); - - await testSubjects.click('codeFileExplorerNode-src'); - await retry.try(async () => { - expect(await exists('codeFileExplorerNode-models')).to.be(true); - }); - - await testSubjects.click('codeFileExplorerNode-models'); - // Then the 'models' folder on the file tree. - await retry.try(async () => { - expect(await exists('codeFileExplorerNode-User.ts')).to.be(true); - }); - - await testSubjects.click('codeFileExplorerNode-User.ts'); - // Then the 'User.ts' file on the file tree. - await retry.try(async () => { - expect(await exists('codeSourceViewer')).to.be(true); - }); - }); - - it.skip('Navigate source file via structure tree', async () => { - log.debug('Navigate source file via structure tree'); - // Wait the file tree to be rendered and click the 'src' folder on the file tree. - await retry.try(async () => { - expect(await exists('codeFileExplorerNode-src')).to.be(true); - }); - - await testSubjects.click('codeFileExplorerNode-src'); - await retry.try(async () => { - expect(await exists('codeFileExplorerNode-models')).to.be(true); - }); - - await testSubjects.click('codeFileExplorerNode-models'); - // Then the 'models' folder on the file tree. - await retry.try(async () => { - expect(await exists('codeFileExplorerNode-User.ts')).to.be(true); - }); - - await testSubjects.click('codeFileExplorerNode-User.ts'); - // Then the 'User.ts' file on the file tree. - await retry.try(async () => { - expect(await exists('codeSourceViewer')).to.be(true); - expect(await exists('codeStructureTreeTab')).to.be(true); - }); - - // Click the structure tree tab - await testSubjects.clickWhenNotDisabled('codeStructureTreeTab'); - await retry.tryForTime(300000, async () => { - expect(await exists('codeStructureTreeNode-User')).to.be(true); - - await testSubjects.click('codeStructureTreeNode-User'); - await retry.tryForTime(120000, async () => { - const currentUrl: string = await browser.getCurrentUrl(); - log.info(`Jump to url: ${currentUrl}`); - expect(currentUrl.indexOf('src/models/User.ts!L92:6') > 0).to.be(true); - }); - }); - }); - - it('goes to a repository which does not exist should render the 404 error page', async () => { - log.debug('it goes to a repository which does not exist'); - const notExistRepoUri = 'github.com/I_DO_NOT_EXIST/I_DO_NOT_EXIST'; - const url = `${PageObjects.common.getHostPort()}/app/code#/${notExistRepoUri}`; - await browser.get(url); - await PageObjects.header.awaitKibanaChrome(); - - await retry.try(async () => { - const currentUrl: string = await browser.getCurrentUrl(); - // should redirect to main page - expect(currentUrl.indexOf(`${notExistRepoUri}/tree/master`)).to.greaterThan(0); - }); - await retry.tryForTime(5000, async () => { - expect(await exists('codeNotFoundErrorPage')).ok(); - }); - }); - - it('goes to a branch of a project', async () => { - log.debug('it goes to a branch of the repo'); - await retry.try(async () => { - expect(exists('codeBranchSelector')); - }); - await testSubjects.click('codeBranchSelector'); - const branch = 'addAzure'; - const branchOptionSelector = `codeBranchSelectOption-${branch}`; - await retry.try(async () => { - expect(exists(branchOptionSelector)); - }); - await testSubjects.click(branchOptionSelector); - await retry.try(async () => { - const currentUrl: string = await browser.getCurrentUrl(); - expect(currentUrl.indexOf(branch.replace(/\//g, ':'))).to.greaterThan(0); - expect(exists(`codeBranchSelectOption-${branch}Active`)).to.be.ok(); - }); - await retry.try(async () => { - expect(exists('codeBranchSelector')); - }); - await testSubjects.click('codeBranchSelector'); - const anotherBranch = 'noDatabase'; - const anotherBranchOptionSelector = `codeBranchSelectOption-${anotherBranch}`; - await retry.try(async () => { - expect(exists(anotherBranchOptionSelector)); - }); - await testSubjects.click(anotherBranchOptionSelector); - await retry.try(async () => { - const currentUrl: string = await browser.getCurrentUrl(); - expect(currentUrl.indexOf(anotherBranch.replace(/\//g, ':'))).to.greaterThan(0); - expect(exists(`codeBranchSelectOption-${anotherBranch}Active`)).to.be.ok(); - }); - }); - }); - }); -} diff --git a/x-pack/test/functional/apps/code/file_tree.ts b/x-pack/test/functional/apps/code/file_tree.ts deleted file mode 100644 index 6fa56610b77f6..0000000000000 --- a/x-pack/test/functional/apps/code/file_tree.ts +++ /dev/null @@ -1,103 +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 { REPO_ROOT } from '@kbn/dev-utils'; -import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../ftr_provider_context'; -import { load as repoLoad, unload as repoUnload } from './repo_archiver'; - -export default function exploreRepositoryFunctionalTests({ - getService, - getPageObjects, -}: FtrProviderContext) { - const esArchiver = getService('esArchiver'); - const browser = getService('browser'); - const testSubjects = getService('testSubjects'); - const retry = getService('retry'); - const config = getService('config'); - const PageObjects = getPageObjects(['common', 'header', 'security', 'code', 'home']); - const exists = async (selector: string) => - await testSubjects.exists(selector, { allowHidden: true }); - - // FLAKY: https://github.com/elastic/kibana/issues/48048 - describe.skip('File Tree', function() { - this.tags('smoke'); - const repositoryListSelector = 'codeRepositoryList > codeRepositoryItem'; - - before(async () => { - await repoLoad( - 'github.com/elastic/code-examples_flatten-directory', - 'code_examples_flatten_directory', - config.get('kbnTestServer.installDir') || REPO_ROOT - ); - await esArchiver.load('code/repositories/code_examples_flatten_directory'); - }); - - beforeEach(async () => { - // Navigate to the code app. - await PageObjects.common.navigateToApp('code'); - await PageObjects.header.waitUntilLoadingHasFinished(); - - // Enter the first repository from the admin page. - await testSubjects.click(repositoryListSelector); - }); - - after(async () => { - await PageObjects.security.forceLogout(); - await repoUnload( - 'github.com/elastic/code-examples_flatten-directory', - config.get('kbnTestServer.installDir') || REPO_ROOT - ); - await esArchiver.unload('code/repositories/code_examples_flatten_directory'); - }); - - it('tree should be loaded', async () => { - await retry.tryForTime(5000, async () => { - expect(await exists('codeFileTreeNode-Directory-elastic/src/code')).ok(); - expect(await exists('codeFileTreeNode-Directory-kibana/src/code')).ok(); - expect(await exists('codeFileTreeNode-File-README.MD')).ok(); - }); - }); - - it('Click file/directory on the file tree', async () => { - await testSubjects.click('codeFileTreeNode-Directory-elastic/src/code'); - - await retry.tryForTime(1000, async () => { - // should only open one folder at this time - expect(await exists('codeFileTreeNode-Directory-Icon-elastic/src/code-open')).ok(); - expect(await exists('codeFileTreeNode-Directory-Icon-kibana/src/code-closed')).ok(); - }); - - await browser.refresh(); - - await PageObjects.header.awaitKibanaChrome(); - - await testSubjects.waitForDeleted('.euiLoadingSpinner'); - - await retry.tryForTime(30000, async () => { - // should only open one folder at this time - expect(await exists('codeFileTreeNode-Directory-Icon-elastic/src/code-open')).ok(); - expect(await exists('codeFileTreeNode-Directory-Icon-kibana/src/code-closed')).ok(); - }); - - await testSubjects.click('codeFileTreeNode-Directory-kibana/src/code'); - - await retry.tryForTime(1000, async () => { - // should open two folders at this time - expect(await exists('codeFileTreeNode-Directory-Icon-elastic/src/code-open')).ok(); - expect(await exists('codeFileTreeNode-Directory-Icon-kibana/src/code-open')).ok(); - }); - - await testSubjects.click('codeFileTreeNode-Directory-elastic/src/code'); - - await retry.tryForTime(1000, async () => { - // should only open one folder at this time - expect(await exists('codeFileTreeNode-Directory-Icon-elastic/src/code-closed')).ok(); - expect(await exists('codeFileTreeNode-Directory-Icon-kibana/src/code-open')).ok(); - }); - }); - }); -} diff --git a/x-pack/test/functional/apps/code/fixtures/code_examples_flatten_directory.zip b/x-pack/test/functional/apps/code/fixtures/code_examples_flatten_directory.zip deleted file mode 100644 index 7b5a4806ee1cb..0000000000000 Binary files a/x-pack/test/functional/apps/code/fixtures/code_examples_flatten_directory.zip and /dev/null differ diff --git a/x-pack/test/functional/apps/code/fixtures/typescript_node_starter.zip b/x-pack/test/functional/apps/code/fixtures/typescript_node_starter.zip deleted file mode 100644 index 628b23a44296e..0000000000000 Binary files a/x-pack/test/functional/apps/code/fixtures/typescript_node_starter.zip and /dev/null differ diff --git a/x-pack/test/functional/apps/code/history.ts b/x-pack/test/functional/apps/code/history.ts deleted file mode 100644 index a0442d1e3fb2e..0000000000000 --- a/x-pack/test/functional/apps/code/history.ts +++ /dev/null @@ -1,247 +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 { REPO_ROOT } from '@kbn/dev-utils'; -import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../ftr_provider_context'; -import { load as repoLoad, unload as repoUnload } from './repo_archiver'; - -export default function manageRepositoriesFunctionalTests({ - getService, - getPageObjects, -}: FtrProviderContext) { - const esArchiver = getService('esArchiver'); - const testSubjects = getService('testSubjects'); - const retry = getService('retry'); - const log = getService('log'); - const browser = getService('browser'); - const queryBar = getService('queryBar'); - const config = getService('config'); - const FIND_TIME = config.get('timeouts.find'); - const find = getService('find'); - const PageObjects = getPageObjects(['common', 'header', 'security', 'code', 'home']); - - const existsInvisible = async (selector: string) => - await testSubjects.exists(selector, { allowHidden: true }); - - // FLAKY: https://github.com/elastic/kibana/issues/46313 - describe.skip('History', function() { - this.tags('smoke'); - const repositoryListSelector = 'codeRepositoryList > codeRepositoryItem'; - - describe('browser history can go back while exploring code app', () => { - before(async () => { - await repoLoad( - 'github.com/elastic/TypeScript-Node-Starter', - 'typescript_node_starter', - config.get('kbnTestServer.installDir') || REPO_ROOT - ); - await esArchiver.load('code/repositories/typescript_node_starter'); - - // Navigate to the code app. - await PageObjects.common.navigateToApp('code'); - await PageObjects.header.waitUntilLoadingHasFinished(); - }); - - after(async () => { - await PageObjects.security.forceLogout(); - await esArchiver.unload('code/repositories/typescript_node_starter'); - await repoUnload( - 'github.com/elastic/TypeScript-Node-Starter', - config.get('kbnTestServer.installDir') || REPO_ROOT - ); - }); - - it('from admin page to source view page can go back and forward', async () => { - await retry.tryForTime(300000, async () => { - const repositoryItems = await testSubjects.findAll(repositoryListSelector); - expect(repositoryItems).to.have.length(1); - expect(await repositoryItems[0].getVisibleText()).to.equal( - 'elastic/TypeScript-Node-Starter' - ); - }); - - await retry.try(async () => { - expect(await testSubjects.exists('repositoryIndexDone')).to.be(true); - - log.debug('it goes to elastic/TypeScript-Node-Starter project page'); - await testSubjects.click('adminLinkToTypeScript-Node-Starter'); - await retry.try(async () => { - expect(await testSubjects.exists('codeStructureTreeTab')).to.be(true); - }); - - // can go back to admin page - await browser.goBack(); - - await retry.tryForTime(300000, async () => { - const repositoryItems = await testSubjects.findAll(repositoryListSelector); - expect(repositoryItems).to.have.length(1); - expect(await repositoryItems[0].getVisibleText()).to.equal( - 'elastic/TypeScript-Node-Starter' - ); - }); - - // can go forward to source view page - await browser.goForward(); - - await retry.try(async () => { - expect(await testSubjects.exists('codeStructureTreeTab')).to.be(true); - }); - }); - }); - - it('from source view page to search page can go back and forward', async () => { - log.debug('it search a symbol'); - await queryBar.setQuery('user'); - await queryBar.submitQuery(); - await retry.try(async () => { - const searchResultListSelector = 'codeSearchResultList codeSearchResultFileItem'; - const results = await testSubjects.findAll(searchResultListSelector); - expect(results).to.be.ok(); - }); - log.debug('it goes back to project page'); - await browser.goBack(); - retry.try(async () => { - expect(await testSubjects.exists('codeStructureTreeTab')).to.be(true); - }); - - await browser.goForward(); - - await retry.try(async () => { - const searchResultListSelector = 'codeSearchResultList codeSearchResultFileItem'; - const results = await testSubjects.findAll(searchResultListSelector); - expect(results).to.be.ok(); - }); - }); - - it('in search page, change language filters can go back and forward', async () => { - log.debug('it select typescript language filter'); - const url = `${PageObjects.common.getHostPort()}/app/code#/search?q=string&langs=typescript`; - await browser.get(url); - - await PageObjects.header.awaitKibanaChrome(); - - await retry.tryForTime(300000, async () => { - const language = await (await find.byCssSelector( - '.euiFacetButton--isSelected' - )).getVisibleText(); - - expect(language.indexOf('typescript')).to.equal(0); - }); - - const unselectedFilter = (await find.allByCssSelector('.euiFacetButton--unSelected'))[1]; - await unselectedFilter.click(); - - await retry.try(async () => { - const l = await (await find.allByCssSelector( - '.euiFacetButton--isSelected' - ))[1].getVisibleText(); - - expect(l.indexOf('javascript')).to.equal(0); - }); - - await browser.goBack(); - - await retry.try(async () => { - const lang = await (await find.byCssSelector( - '.euiFacetButton--isSelected' - )).getVisibleText(); - - expect(lang.indexOf('typescript')).to.equal(0); - }); - - await browser.goForward(); - - await retry.try(async () => { - const filter = await (await find.allByCssSelector( - '.euiFacetButton--isSelected' - ))[1].getVisibleText(); - - expect(filter.indexOf('javascript')).to.equal(0); - }); - }); - - it('in source view page file line number changed can go back and forward', async () => { - log.debug('it goes back after line number changed'); - const url = `${PageObjects.common.getHostPort()}/app/code#/github.com/elastic/TypeScript-Node-Starter`; - await browser.get(url); - await PageObjects.header.awaitKibanaChrome(); - - const lineNumber = 20; - await retry.try(async () => { - const existence = await existsInvisible('codeFileTreeNode-File-tsconfig.json'); - expect(existence).to.be(true); - }); - await testSubjects.click('codeFileTreeNode-File-tsconfig.json'); - await retry.try(async () => { - const existence = await existsInvisible('codeFileTreeNode-File-package.json'); - expect(existence).to.be(true); - }); - await testSubjects.click('codeFileTreeNode-File-package.json'); - - await retry.try(async () => { - const currentUrl: string = await browser.getCurrentUrl(); - // click line number should stay in the same file - expect(currentUrl.indexOf('package.json')).greaterThan(0); - }); - - const lineNumberElements = await find.allByCssSelector('.line-numbers'); - await lineNumberElements[lineNumber].click(); - - await retry.try(async () => { - const existence = await find.existsByCssSelector('.code-line-number-21', FIND_TIME); - expect(existence).to.be(true); - }); - - await browser.goBack(); - - await retry.try(async () => { - const existence = await find.existsByCssSelector('.code-line-number-21', FIND_TIME); - expect(existence).to.be(false); - }); - - await browser.goForward(); - - await retry.try(async () => { - const existence = await find.existsByCssSelector('.code-line-number-21', FIND_TIME); - expect(existence).to.be(true); - }); - }); - - it('in source view page, switch side tab can go back and forward', async () => { - log.debug('it goes back after line number changed'); - const url = `${PageObjects.common.getHostPort()}/app/code#/github.com/elastic/TypeScript-Node-Starter/blob/master/src/controllers/api.ts`; - await browser.get(url); - // refresh so language server will be initialized. - await browser.refresh(); - - await PageObjects.header.awaitKibanaChrome(); - - // wait for tab is not disabled - await PageObjects.common.sleep(5000); - await testSubjects.click('codeStructureTreeTab'); - await retry.try(async () => { - // if structure tree tab is active, file tree tab's `data-test-subj` would be `codeFileTreeTab` - expect(testSubjects.exists('codeFileTreeTab')).to.be.ok(); - }); - - await browser.goBack(); - - await retry.try(async () => { - // if file tree tab is active, file tree tab's `data-test-subj` would be `codeFileTreeTabActive` - expect(testSubjects.exists('codeFileTreeTabActive')).to.be.ok(); - }); - - await browser.goForward(); - - await retry.try(async () => { - // if structure tree tab is active, file tree tab's `data-test-subj` would be `codeFileTreeTab` - expect(testSubjects.exists('codeFileTreeTab')).to.be.ok(); - }); - }); - }); - }); -} diff --git a/x-pack/test/functional/apps/code/index.ts b/x-pack/test/functional/apps/code/index.ts deleted file mode 100644 index a08d715f3ac7e..0000000000000 --- a/x-pack/test/functional/apps/code/index.ts +++ /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 { FtrProviderContext } from '../../ftr_provider_context'; - -export default function codeApp({ loadTestFile }: FtrProviderContext) { - describe('Code', function codeAppTestSuite() { - // Add 'skipCloud' regarding issue: https://github.com/elastic/kibana/issues/39386 - this.tags(['ciGroup2', 'skipCloud']); - loadTestFile(require.resolve('./lang_server_coverage')); - loadTestFile(require.resolve('./manage_repositories')); - loadTestFile(require.resolve('./search')); - loadTestFile(require.resolve('./explore_repository')); - loadTestFile(require.resolve('./code_intelligence')); - loadTestFile(require.resolve('./history')); - loadTestFile(require.resolve('./file_tree')); - loadTestFile(require.resolve('./with_security')); - }); -} diff --git a/x-pack/test/functional/apps/code/lang_server_coverage.ts b/x-pack/test/functional/apps/code/lang_server_coverage.ts deleted file mode 100644 index 791a0b2c0bf44..0000000000000 --- a/x-pack/test/functional/apps/code/lang_server_coverage.ts +++ /dev/null @@ -1,137 +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 expect from '@kbn/expect'; -import { FtrProviderContext } from '../../ftr_provider_context'; - -export default function langServerCoverageFunctionalTests({ - getService, - getPageObjects, -}: FtrProviderContext) { - const testSubjects = getService('testSubjects'); - const retry = getService('retry'); - const esSupertest = getService('esSupertest'); - const PageObjects = getPageObjects(['common', 'header', 'security', 'code', 'home']); - - // Use https://github.com/elastic/code-examples_smoke repository to smoke test - // all language servers. - // FLAKY: https://github.com/elastic/kibana/issues/44576 - describe.skip('Lang Server Coverage', () => { - const repositoryListSelector = 'codeRepositoryList codeRepositoryItem'; - before(async () => { - // Navigate to the code app. - await PageObjects.common.navigateToApp('code'); - await PageObjects.header.waitUntilLoadingHasFinished(); - - // Prepare a git repository for the test - await PageObjects.code.fillImportRepositoryUrlInputBox( - 'https://github.com/elastic/code-examples_smoke' - ); - // Click the import repository button. - await PageObjects.code.clickImportRepositoryButton(); - - await retry.tryForTime(300000, async () => { - const repositoryItems = await testSubjects.findAll(repositoryListSelector); - expect(repositoryItems).to.have.length(1); - expect(await repositoryItems[0].getVisibleText()).to.equal('elastic/code-examples_smoke'); - - // Wait for the index to start. - await retry.try(async () => { - expect(await testSubjects.exists('repositoryIndexOngoing')).to.be(true); - }); - // Wait for the index to end. - await retry.try(async () => { - expect(await testSubjects.exists('repositoryIndexDone')).to.be(true); - }); - }); - }); - - after(async () => { - // Navigate to the code app. - await PageObjects.common.navigateToApp('code'); - await PageObjects.header.waitUntilLoadingHasFinished(); - - // Clean up the imported repository - await PageObjects.code.clickDeleteRepositoryButton(); - await retry.try(async () => { - expect(await testSubjects.exists('confirmModalConfirmButton')).to.be(true); - }); - - await testSubjects.click('confirmModalConfirmButton'); - - await retry.tryForTime(300000, async () => { - const repositoryItems = await testSubjects.findAll(repositoryListSelector); - expect(repositoryItems).to.have.length(0); - }); - await retry.tryForTime(300000, async () => { - const repositoryItems = await testSubjects.findAll(repositoryListSelector); - expect(repositoryItems).to.have.length(0); - }); - - await PageObjects.security.forceLogout(); - }); - - it('Verify the symbol/referernces object counts in Elasticsearch', async () => { - // Make sure the indexes exist. - await esSupertest - .head('/.code-symbol-github.com-elastic-code-examples_smoke-19c9057c-1') - .expect(200); - await esSupertest - .head('/.code-reference-github.com-elastic-code-examples_smoke-19c9057c-1') - .expect(200); - - const symbolLangAggs = await esSupertest - .post('/.code-symbol-github.com-elastic-code-examples_smoke-19c9057c-1/_search') - .send({ - query: { - match_all: {}, - }, - aggs: { - language: { - terms: { - script: { - // Aggregate results based on the source file's extension. - source: - "doc['symbolInformation.location.uri'].value.substring(doc['symbolInformation.location.uri'].value.lastIndexOf('.'))", - lang: 'painless', - }, - }, - }, - }, - }) - .expect(200) - .then((res: any) => res.body); - - // Symbol's source file extension aggregations - expect(JSON.stringify(symbolLangAggs.aggregations.language.buckets.sort())).to.equal( - JSON.stringify( - [ - { - key: '.java', - doc_count: 3, - }, - { - key: '.py', - doc_count: 2, - }, - { - key: '.ts', - doc_count: 2, - }, - ].sort() - ) - ); - expect(symbolLangAggs.hits.total.value).to.equal(7); - - const referenceCount = await esSupertest - .get('/.code-reference-github.com-elastic-code-examples_smoke-19c9057c-1/_count') - .expect(200) - .then((res: any) => res.body); - - expect(referenceCount.count).to.equal(0); - }); - }); -} diff --git a/x-pack/test/functional/apps/code/manage_repositories.ts b/x-pack/test/functional/apps/code/manage_repositories.ts deleted file mode 100644 index 8eff8868a4756..0000000000000 --- a/x-pack/test/functional/apps/code/manage_repositories.ts +++ /dev/null @@ -1,114 +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 expect from '@kbn/expect'; -import { FtrProviderContext } from '../../ftr_provider_context'; - -export default function manageRepositoriesFunctionalTests({ - getService, - getPageObjects, -}: FtrProviderContext) { - // const esArchiver = getService('esArchiver'); - const testSubjects = getService('testSubjects'); - const retry = getService('retry'); - const log = getService('log'); - const PageObjects = getPageObjects(['common', 'header', 'security', 'code', 'home']); - - describe('Manage Repositories', function() { - this.tags('smoke'); - const repositoryListSelector = 'codeRepositoryList > codeRepositoryItem'; - - describe('Manage Repositories', () => { - before(async () => { - // Navigate to the code app. - await PageObjects.common.navigateToApp('code'); - await PageObjects.header.waitUntilLoadingHasFinished(); - }); - - after(async () => { - await PageObjects.security.forceLogout(); - }); - - it('import repository', async () => { - log.debug('Code test import repository'); - // Fill in the import repository input box with a valid git repository url. - await PageObjects.code.fillImportRepositoryUrlInputBox( - 'https://github.com/elastic/code-examples_empty-file' - ); - // Click the import repository button. - await PageObjects.code.clickImportRepositoryButton(); - - await retry.tryForTime(300000, async () => { - const repositoryItems = await testSubjects.findAll(repositoryListSelector); - expect(repositoryItems).to.have.length(1); - expect(await repositoryItems[0].getVisibleText()).to.equal( - 'elastic/code-examples_empty-file' - ); - }); - - // Wait for the index to end. - await retry.try(async () => { - expect(await testSubjects.exists('repositoryIndexDone')).to.be(true); - }); - }); - - it('delete repository', async () => { - log.debug('Code test delete repository'); - // Click the delete repository button. - await PageObjects.code.clickDeleteRepositoryButton(); - - await retry.try(async () => { - expect(await testSubjects.exists('confirmModalConfirmButton')).to.be(true); - }); - - await testSubjects.click('confirmModalConfirmButton'); - - await retry.tryForTime(300000, async () => { - const repositoryItems = await testSubjects.findAll(repositoryListSelector); - expect(repositoryItems).to.have.length(0); - }); - }); - - it('import a git:// repository', async () => { - log.debug('Code test import repository'); - // Fill in the import repository input box with a valid git repository url. - await PageObjects.code.fillImportRepositoryUrlInputBox( - 'git://github.com/elastic/code-examples_empty-file' - ); - - // Click the import repository button. - await PageObjects.code.clickImportRepositoryButton(); - - await retry.tryForTime(300000, async () => { - const repositoryItems = await testSubjects.findAll(repositoryListSelector); - expect(repositoryItems).to.have.length(1); - expect(await repositoryItems[0].getVisibleText()).to.equal( - 'elastic/code-examples_empty-file' - ); - }); - - // Wait for the index to end. - await retry.try(async () => { - expect(await testSubjects.exists('repositoryIndexDone')).to.be(true); - }); - - // Delete the repository - await PageObjects.code.clickDeleteRepositoryButton(); - - await retry.try(async () => { - expect(await testSubjects.exists('confirmModalConfirmButton')).to.be(true); - }); - - await testSubjects.click('confirmModalConfirmButton'); - - await retry.tryForTime(300000, async () => { - const repositoryItems = await testSubjects.findAll(repositoryListSelector); - expect(repositoryItems).to.have.length(0); - }); - }); - }); - }); -} diff --git a/x-pack/test/functional/apps/code/repo_archiver.ts b/x-pack/test/functional/apps/code/repo_archiver.ts deleted file mode 100644 index fc6baaab57423..0000000000000 --- a/x-pack/test/functional/apps/code/repo_archiver.ts +++ /dev/null @@ -1,48 +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 del from 'del'; -// @ts-ignore -import extractZip from 'extract-zip'; -import fs from 'fs'; -import path from 'path'; -import { promisify } from 'util'; - -const asyncExtractZip = promisify(extractZip); -const asyncMkdir = promisify(fs.mkdir); - -const archiveFilePath = (zipFileName: string) => { - return path.resolve(__dirname, `./fixtures/${zipFileName}.zip`); -}; - -const repoDir = (repoUri: string, kibanaDir: string) => { - return path.resolve(kibanaDir, `data/code/repos/${repoUri}`); -}; - -const workspaceDir = (repoUri: string, kibanaDir: string) => { - return path.resolve(kibanaDir, `data/code/workspace/${repoUri}`); -}; - -const unzip = async (filepath: string, target: string) => { - if (!fs.existsSync(target)) { - await asyncMkdir(target, { recursive: true }); - } - await asyncExtractZip(filepath, { dir: target }); -}; - -export const load = async (repoUri: string, zipFileName: string, kibanaDir: string) => { - const dir = repoDir(repoUri, kibanaDir); - const zipRepoPath = archiveFilePath(zipFileName); - // Try to unload first in case the folder already exists - await unload(repoUri, kibanaDir); - return unzip(zipRepoPath, dir); -}; - -export const unload = async (repoUri: string, kibanaDir: string) => { - const dir = repoDir(repoUri, kibanaDir); - const wsDir = workspaceDir(repoUri, kibanaDir); - return del([dir, wsDir], { force: true }); -}; diff --git a/x-pack/test/functional/apps/code/search.ts b/x-pack/test/functional/apps/code/search.ts deleted file mode 100644 index b3cdb21294a29..0000000000000 --- a/x-pack/test/functional/apps/code/search.ts +++ /dev/null @@ -1,140 +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 expect from '@kbn/expect'; -import { FtrProviderContext } from '../../ftr_provider_context'; - -export default function searchFunctonalTests({ getService, getPageObjects }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); - const testSubjects = getService('testSubjects'); - const retry = getService('retry'); - const log = getService('log'); - const PageObjects = getPageObjects(['common', 'header', 'security', 'code', 'home']); - - describe('Search', function() { - this.tags('smoke'); - const symbolTypeaheadListSelector = 'codeTypeaheadList-symbol > codeTypeaheadItem'; - const fileTypeaheadListSelector = 'codeTypeaheadList-file > codeTypeaheadItem'; - const searchResultListSelector = 'codeSearchResultList > codeSearchResultFileItem'; - const languageFilterListSelector = - 'codeSearchLanguageFilterList > codeSearchLanguageFilterItem'; - - describe('Code Search', () => { - before(async () => { - await esArchiver.load('code/repositories/typescript_node_starter'); - - // Navigate to the search page of the code app. - await PageObjects.common.navigateToApp('codeSearch'); - await PageObjects.header.waitUntilLoadingHasFinished(); - }); - - after(async () => { - await PageObjects.security.forceLogout(); - await esArchiver.unload('code/repositories/typescript_node_starter'); - }); - - it('Trigger symbols in typeahead', async () => { - log.debug('Trigger symbols in typeahead'); - await PageObjects.code.fillSearchQuery('user'); - - await retry.tryForTime(5000, async () => { - const symbols = await testSubjects.findAll(symbolTypeaheadListSelector); - expect(symbols).to.have.length(5); - - expect(await symbols[0].getVisibleText()).to.equal('User.findOne() callback.user'); - expect(await symbols[1].getVisibleText()).to.equal('postSignup.user'); - }); - }); - - it('File typeahead should be case insensitive', async () => { - log.debug('File typeahead should be case insensitive'); - await PageObjects.code.fillSearchQuery('LICENSE'); - - await retry.tryForTime(5000, async () => { - const symbols = await testSubjects.findAll(fileTypeaheadListSelector); - expect(symbols).to.have.length(1); - - expect(await symbols[0].getVisibleText()).to.equal('LICENSE'); - }); - - await PageObjects.code.fillSearchQuery('license'); - - await retry.tryForTime(5000, async () => { - const symbols = await testSubjects.findAll(fileTypeaheadListSelector); - expect(symbols).to.have.length(1); - - expect(await symbols[0].getVisibleText()).to.equal('LICENSE'); - }); - }); - - it('Full text search', async () => { - log.debug('Full text search'); - // Fill in the search query bar with a common prefix of symbols. - await PageObjects.code.fillSearchQuery('user'); - await PageObjects.code.submitSearchQuery(); - - await retry.tryForTime(5000, async () => { - const results = await testSubjects.findAll(searchResultListSelector); - expect(results).to.have.length(20); - - // The third file has the most matches of the query, but is still ranked as - // the thrid because the the query matches the qname of the first 2 files. This - // is because qname got boosted more from search. - expect(await results[0].getVisibleText()).to.equal('src/controllers/user.ts'); - expect(await results[1].getVisibleText()).to.equal('src/models/User.ts'); - expect(await results[2].getVisibleText()).to.equal('src/config/passport.ts'); - }); - }); - - it('Full text search with complex query terms', async () => { - log.debug('Full text search with complex query terms'); - // Fill in the search query bar with a complex query which could result in multiple - // terms. - await PageObjects.code.fillSearchQuery('postUpdateProfile'); - await PageObjects.code.submitSearchQuery(); - - await retry.tryForTime(5000, async () => { - const results = await testSubjects.findAll(searchResultListSelector); - expect(results).to.have.length(2); - expect(await results[0].getVisibleText()).to.equal('src/app.ts'); - expect(await results[1].getVisibleText()).to.equal('src/controllers/user.ts'); - }); - }); - - it('Apply language filter', async () => { - log.debug('Apply language filter'); - // Fill in the search query bar with a common prefix of symbols. - await PageObjects.code.fillSearchQuery('user'); - await PageObjects.code.submitSearchQuery(); - - await retry.tryForTime(5000, async () => { - const langFilters = await testSubjects.findAll(languageFilterListSelector); - expect(langFilters).to.have.length(4); - - expect(await langFilters[0].getVisibleText()).to.equal('scss\n9'); - expect(await langFilters[1].getVisibleText()).to.equal('typescript\n7'); - expect(await langFilters[2].getVisibleText()).to.equal('pug\n5'); - expect(await langFilters[3].getVisibleText()).to.equal('markdown\n1'); - }); - - await retry.tryForTime(5000, async () => { - // click the first language filter item. - await testSubjects.click(languageFilterListSelector); - - const results = await testSubjects.findAll(searchResultListSelector); - expect(results).to.have.length(9); - - expect(await results[0].getVisibleText()).to.equal( - 'src/public/css/lib/bootstrap/mixins/_vendor-prefixes.scss' - ); - expect(await results[1].getVisibleText()).to.equal( - 'src/public/css/themes/gsdk/gsdk/mixins/_vendor-prefixes.scss' - ); - }); - }); - }); - }); -} diff --git a/x-pack/test/functional/apps/code/with_security.ts b/x-pack/test/functional/apps/code/with_security.ts deleted file mode 100644 index 6e33216621942..0000000000000 --- a/x-pack/test/functional/apps/code/with_security.ts +++ /dev/null @@ -1,135 +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 { REPO_ROOT } from '@kbn/dev-utils'; -import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../ftr_provider_context'; -import { load as repoLoad, unload as repoUnload } from './repo_archiver'; - -export default function testWithSecurity({ getService, getPageObjects }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); - const testSubjects = getService('testSubjects'); - const retry = getService('retry'); - const PageObjects = getPageObjects(['common', 'header', 'security', 'code', 'home', 'settings']); - const dummyPassword = '123321'; - const codeAdmin = 'codeAdmin'; - const codeUser = 'codeUser'; - const repositoryListSelector = 'codeRepositoryList > codeRepositoryItem'; - const manageButtonSelectors = ['indexRepositoryButton', 'deleteRepositoryButton']; - const log = getService('log'); - const security = getService('security'); - const config = getService('config'); - - // FLAKY: https://github.com/elastic/kibana/issues/46977 - describe.skip('Security', () => { - describe('with security enabled:', () => { - before(async () => { - await esArchiver.load('empty_kibana'); - await security.role.create('global_code_all_role', { - kibana: [ - { - feature: { - code: ['all'], - }, - spaces: ['*'], - }, - ], - }); - - await security.user.create(codeAdmin, { - password: dummyPassword, - roles: ['global_code_all_role'], - full_name: 'code admin', - }); - - await security.role.create('global_code_read_role', { - kibana: [ - { - feature: { - code: ['read'], - }, - spaces: ['*'], - }, - ], - }); - - await security.user.create(codeUser, { - password: dummyPassword, - roles: ['global_code_read_role'], - full_name: 'code user', - }); - }); - - after(async () => { - await PageObjects.security.forceLogout(); - await esArchiver.unload('empty_kibana'); - }); - - async function login(user: string) { - await PageObjects.security.forceLogout(); - await PageObjects.security.login(user, dummyPassword); - await PageObjects.common.navigateToApp('code'); - await PageObjects.header.waitUntilLoadingHasFinished(); - } - - it('codeAdmin should have an import button', async () => { - await login(codeAdmin); - await retry.tryForTime(5000, async () => { - const buttons = await testSubjects.findAll('importRepositoryButton'); - expect(buttons).to.have.length(1); - }); - }); - it('codeUser should not have that import button', async () => { - await login(codeUser); - await retry.tryForTime(5000, async () => { - const buttons = await testSubjects.findAll('importRepositoryButton'); - expect(buttons).to.have.length(0); - }); - }); - - it('only codeAdmin can manage repositories', async () => { - await repoLoad( - 'github.com/elastic/TypeScript-Node-Starter', - 'typescript_node_starter', - config.get('kbnTestServer.installDir') || REPO_ROOT - ); - await esArchiver.load('code/repositories/typescript_node_starter'); - - { - await login(codeAdmin); - const repositoryItems = await testSubjects.findAll(repositoryListSelector); - expect(repositoryItems).to.have.length(1); - for (const buttonSelector of manageButtonSelectors) { - const buttons = await testSubjects.findAll(buttonSelector); - expect(buttons).to.have.length(1); - log.debug(`button ${buttonSelector} found.`); - } - const importButton = await testSubjects.findAll('newProjectButton'); - expect(importButton).to.have.length(1); - log.debug(`button newProjectButton found.`); - } - - { - await login(codeUser); - const repositoryItems = await testSubjects.findAll(repositoryListSelector); - expect(repositoryItems).to.have.length(1); - for (const buttonSelector of manageButtonSelectors) { - const buttons = await testSubjects.findAll(buttonSelector); - expect(buttons).to.have.length(0); - } - const importButton = await testSubjects.findAll('newProjectButton'); - expect(importButton).to.have.length(0); - } - - await esArchiver.unload('code/repositories/typescript_node_starter'); - await repoUnload( - 'github.com/elastic/TypeScript-Node-Starter', - config.get('kbnTestServer.installDir') || REPO_ROOT - ); - }); - }); - }); -} diff --git a/x-pack/test/functional/apps/logstash/pipeline_list.js b/x-pack/test/functional/apps/logstash/pipeline_list.js index d04f50690368d..ce0c9d881f51b 100644 --- a/x-pack/test/functional/apps/logstash/pipeline_list.js +++ b/x-pack/test/functional/apps/logstash/pipeline_list.js @@ -22,6 +22,9 @@ export default function ({ getService, getPageObjects }) { originalWindowSize = await browser.getWindowSize(); await browser.setWindowSize(1600, 1000); await esArchiver.load('logstash/example_pipelines'); + }); + + beforeEach(async () => { await PageObjects.logstash.gotoPipelineList(); }); @@ -86,10 +89,6 @@ export default function ({ getService, getPageObjects }) { await pipelineEditor.assertExists(); await pipelineEditor.assertDefaultInputs(); }); - - after(async () => { - await PageObjects.logstash.gotoPipelineList(); - }); }); describe('delete button', () => { @@ -122,15 +121,12 @@ export default function ({ getService, getPageObjects }) { describe('row links', () => { it('opens the selected row in the editor', async () => { + await PageObjects.logstash.gotoPipelineList(); await pipelineList.setFilter('tweets_and_beats'); await pipelineList.clickFirstRowId(); await pipelineEditor.assertExists(); await pipelineEditor.assertEditorId('tweets_and_beats'); }); - - after(async () => { - await PageObjects.logstash.gotoPipelineList(); - }); }); describe('next page button', () => { @@ -225,10 +221,6 @@ export default function ({ getService, getPageObjects }) { queueCheckpointWrites, }); }); - - after(async () => { - await PageObjects.logstash.gotoPipelineList(); - }); }); }); } diff --git a/x-pack/test/functional/apps/machine_learning/anomaly_detection/multi_metric_job.ts b/x-pack/test/functional/apps/machine_learning/anomaly_detection/multi_metric_job.ts index c4e6459e10555..6163e99b5eaa4 100644 --- a/x-pack/test/functional/apps/machine_learning/anomaly_detection/multi_metric_job.ts +++ b/x-pack/test/functional/apps/machine_learning/anomaly_detection/multi_metric_job.ts @@ -74,7 +74,7 @@ export default function({ getService }: FtrProviderContext) { describe('multi metric', function() { this.tags(['smoke', 'mlqa']); before(async () => { - await esArchiver.loadIfNeeded('ml/farequote'); + await esArchiver.load('ml/farequote'); }); after(async () => { @@ -82,289 +82,274 @@ export default function({ getService }: FtrProviderContext) { await ml.api.cleanMlIndices(); }); - describe('job creation', function() { - it('loads the job management page', async () => { - await ml.navigation.navigateToMl(); - await ml.navigation.navigateToJobManagement(); - }); - - it('loads the new job source selection page', async () => { - await ml.jobManagement.navigateToNewJobSourceSelection(); - }); - - it('loads the job type selection page', async () => { - await ml.jobSourceSelection.selectSource('farequote'); - }); - - it('loads the multi metric job wizard page', async () => { - await ml.jobTypeSelection.selectMultiMetricJob(); - }); - - it('displays the time range step', async () => { - await ml.jobWizardCommon.assertTimeRangeSectionExists(); - }); - - it('sets the timerange', async () => { - await ml.jobWizardCommon.clickUseFullDataButton( - 'Feb 7, 2016 @ 00:00:00.000', - 'Feb 11, 2016 @ 23:59:54.000' - ); - }); - - it('displays the event rate chart', async () => { - await ml.jobWizardCommon.assertEventRateChartExists(); - await ml.jobWizardCommon.assertEventRateChartHasData(); - }); - - it('displays the pick fields step', async () => { - await ml.jobWizardCommon.advanceToPickFieldsSection(); - }); - - it('selects detectors and displays detector previews', async () => { - for (const [index, aggAndFieldIdentifier] of aggAndFieldIdentifiers.entries()) { - await ml.jobWizardCommon.assertAggAndFieldInputExists(); - await ml.jobWizardCommon.selectAggAndField(aggAndFieldIdentifier, false); - await ml.jobWizardCommon.assertDetectorPreviewExists( - aggAndFieldIdentifier, - index, - 'LINE' - ); - } - }); - - it('inputs the split field and displays split cards', async () => { - await ml.jobWizardMultiMetric.assertSplitFieldInputExists(); - await ml.jobWizardMultiMetric.selectSplitField(splitField); - - await ml.jobWizardMultiMetric.assertDetectorSplitExists(splitField); - await ml.jobWizardMultiMetric.assertDetectorSplitFrontCardTitle('AAL'); - await ml.jobWizardMultiMetric.assertDetectorSplitNumberOfBackCards(9); - - await ml.jobWizardCommon.assertInfluencerSelection([splitField]); - }); - - it('displays the influencer field', async () => { - await ml.jobWizardCommon.assertInfluencerInputExists(); - await ml.jobWizardCommon.assertInfluencerSelection([splitField]); - }); - - it('inputs the bucket span', async () => { - await ml.jobWizardCommon.assertBucketSpanInputExists(); - await ml.jobWizardCommon.setBucketSpan(bucketSpan); - }); - - it('displays the job details step', async () => { - await ml.jobWizardCommon.advanceToJobDetailsSection(); - }); - - it('inputs the job id', async () => { - await ml.jobWizardCommon.assertJobIdInputExists(); - await ml.jobWizardCommon.setJobId(jobId); - }); - - it('inputs the job description', async () => { - await ml.jobWizardCommon.assertJobDescriptionInputExists(); - await ml.jobWizardCommon.setJobDescription(jobDescription); - }); - - it('inputs job groups', async () => { - await ml.jobWizardCommon.assertJobGroupInputExists(); - for (const jobGroup of jobGroups) { - await ml.jobWizardCommon.addJobGroup(jobGroup); - } - await ml.jobWizardCommon.assertJobGroupSelection(jobGroups); - }); - - it('opens the advanced section', async () => { - await ml.jobWizardCommon.ensureAdvancedSectionOpen(); - }); - - it('displays the model plot switch', async () => { - await ml.jobWizardCommon.assertModelPlotSwitchExists(); - }); - - it('enables the dedicated index switch', async () => { - await ml.jobWizardCommon.assertDedicatedIndexSwitchExists(); - await ml.jobWizardCommon.activateDedicatedIndexSwitch(); - }); - - it('inputs the model memory limit', async () => { - await ml.jobWizardCommon.assertModelMemoryLimitInputExists(); - await ml.jobWizardCommon.setModelMemoryLimit(memoryLimit); - }); - - it('displays the validation step', async () => { - await ml.jobWizardCommon.advanceToValidationSection(); - }); - - it('displays the summary step', async () => { - await ml.jobWizardCommon.advanceToSummarySection(); - }); - - it('creates the job and finishes processing', async () => { - await ml.jobWizardCommon.assertCreateJobButtonExists(); - await ml.jobWizardCommon.createJobAndWaitForCompletion(); - }); - - it('displays the created job in the job list', async () => { - await ml.navigation.navigateToMl(); - await ml.navigation.navigateToJobManagement(); - - await ml.jobTable.waitForJobsToLoad(); - await ml.jobTable.filterWithSearchString(jobId); - const rows = await ml.jobTable.parseJobTable(); - expect(rows.filter(row => row.id === jobId)).to.have.length(1); - }); - - it('displays details for the created job in the job list', async () => { - await ml.jobTable.assertJobRowFields(jobId, getExpectedRow(jobId, jobGroups)); - - await ml.jobTable.assertJobRowDetailsCounts( - jobId, - getExpectedCounts(jobId), - getExpectedModelSizeStats(jobId) - ); - }); - }); - - describe('job cloning', function() { - it('clicks the clone action and loads the multi metric wizard', async () => { - await ml.jobTable.clickCloneJobAction(jobId); - await ml.jobTypeSelection.assertMultiMetricJobWizardOpen(); - }); - - it('displays the time range step', async () => { - await ml.jobWizardCommon.assertTimeRangeSectionExists(); - }); - - it('sets the timerange', async () => { - await ml.jobWizardCommon.clickUseFullDataButton( - 'Feb 7, 2016 @ 00:00:00.000', - 'Feb 11, 2016 @ 23:59:54.000' - ); - }); - - it('displays the event rate chart', async () => { - await ml.jobWizardCommon.assertEventRateChartExists(); - await ml.jobWizardCommon.assertEventRateChartHasData(); - }); - - it('displays the pick fields step', async () => { - await ml.jobWizardCommon.advanceToPickFieldsSection(); - }); - - it('pre-fills detectors and shows preview with split cards', async () => { - for (const [index, aggAndFieldIdentifier] of aggAndFieldIdentifiers.entries()) { - await ml.jobWizardCommon.assertDetectorPreviewExists( - aggAndFieldIdentifier, - index, - 'LINE' - ); - } - - await ml.jobWizardMultiMetric.assertDetectorSplitExists(splitField); - await ml.jobWizardMultiMetric.assertDetectorSplitFrontCardTitle('AAL'); - await ml.jobWizardMultiMetric.assertDetectorSplitNumberOfBackCards(9); - }); - - it('pre-fills the split field', async () => { - await ml.jobWizardMultiMetric.assertSplitFieldInputExists(); - await ml.jobWizardMultiMetric.assertSplitFieldSelection(splitField); - }); - - it('pre-fills influencers', async () => { - await ml.jobWizardCommon.assertInfluencerInputExists(); - await ml.jobWizardCommon.assertInfluencerSelection([splitField]); - }); - - it('pre-fills the bucket span', async () => { - await ml.jobWizardCommon.assertBucketSpanInputExists(); - await ml.jobWizardCommon.assertBucketSpanValue(bucketSpan); - }); - - it('displays the job details step', async () => { - await ml.jobWizardCommon.advanceToJobDetailsSection(); - }); - - it('does not pre-fill the job id', async () => { - await ml.jobWizardCommon.assertJobIdInputExists(); - await ml.jobWizardCommon.assertJobIdValue(''); - }); - - it('inputs the clone job id', async () => { - await ml.jobWizardCommon.setJobId(jobIdClone); - }); - - it('pre-fills the job description', async () => { - await ml.jobWizardCommon.assertJobDescriptionInputExists(); - await ml.jobWizardCommon.assertJobDescriptionValue(jobDescription); - }); - - it('pre-fills job groups', async () => { - await ml.jobWizardCommon.assertJobGroupInputExists(); - await ml.jobWizardCommon.assertJobGroupSelection(jobGroups); - }); - - it('inputs the clone job group', async () => { - await ml.jobWizardCommon.assertJobGroupInputExists(); - await ml.jobWizardCommon.addJobGroup('clone'); - await ml.jobWizardCommon.assertJobGroupSelection(jobGroupsClone); - }); - - it('opens the advanced section', async () => { - await ml.jobWizardCommon.ensureAdvancedSectionOpen(); - }); - - it('pre-fills the model plot switch', async () => { - await ml.jobWizardCommon.assertModelPlotSwitchExists(); - await ml.jobWizardCommon.assertModelPlotSwitchCheckedState(false); - }); - - it('pre-fills the dedicated index switch', async () => { - await ml.jobWizardCommon.assertDedicatedIndexSwitchExists(); - await ml.jobWizardCommon.assertDedicatedIndexSwitchCheckedState(true); - }); - - it('pre-fills the model memory limit', async () => { - await ml.jobWizardCommon.assertModelMemoryLimitInputExists(); - await ml.jobWizardCommon.assertModelMemoryLimitValue(memoryLimit); - }); - - it('displays the validation step', async () => { - await ml.jobWizardCommon.advanceToValidationSection(); - }); - - it('displays the summary step', async () => { - await ml.jobWizardCommon.advanceToSummarySection(); - }); - - it('creates the job and finishes processing', async () => { - await ml.jobWizardCommon.assertCreateJobButtonExists(); - await ml.jobWizardCommon.createJobAndWaitForCompletion(); - }); - - it('displays the created job in the job list', async () => { - await ml.navigation.navigateToMl(); - await ml.navigation.navigateToJobManagement(); - - await ml.jobTable.waitForJobsToLoad(); - await ml.jobTable.filterWithSearchString(jobIdClone); - const rows = await ml.jobTable.parseJobTable(); - expect(rows.filter(row => row.id === jobIdClone)).to.have.length(1); - }); - - it('displays details for the created job in the job list', async () => { - await ml.jobTable.assertJobRowFields( - jobIdClone, - getExpectedRow(jobIdClone, jobGroupsClone) - ); - - await ml.jobTable.assertJobRowDetailsCounts( - jobIdClone, - getExpectedCounts(jobIdClone), - getExpectedModelSizeStats(jobIdClone) - ); - }); + it('job creation loads the job management page', async () => { + await ml.navigation.navigateToMl(); + await ml.navigation.navigateToJobManagement(); + }); + + it('job creation loads the new job source selection page', async () => { + await ml.jobManagement.navigateToNewJobSourceSelection(); + }); + + it('job creation loads the job type selection page', async () => { + await ml.jobSourceSelection.selectSource('farequote'); + }); + + it('job creation loads the multi metric job wizard page', async () => { + await ml.jobTypeSelection.selectMultiMetricJob(); + }); + + it('job creation displays the time range step', async () => { + await ml.jobWizardCommon.assertTimeRangeSectionExists(); + }); + + it('job creation sets the timerange', async () => { + await ml.jobWizardCommon.clickUseFullDataButton( + 'Feb 7, 2016 @ 00:00:00.000', + 'Feb 11, 2016 @ 23:59:54.000' + ); + }); + + it('job creation displays the event rate chart', async () => { + await ml.jobWizardCommon.assertEventRateChartExists(); + await ml.jobWizardCommon.assertEventRateChartHasData(); + }); + + it('job creation displays the pick fields step', async () => { + await ml.jobWizardCommon.advanceToPickFieldsSection(); + }); + + it('job creation selects detectors and displays detector previews', async () => { + for (const [index, aggAndFieldIdentifier] of aggAndFieldIdentifiers.entries()) { + await ml.jobWizardCommon.assertAggAndFieldInputExists(); + await ml.jobWizardCommon.selectAggAndField(aggAndFieldIdentifier, false); + await ml.jobWizardCommon.assertDetectorPreviewExists(aggAndFieldIdentifier, index, 'LINE'); + } + }); + + it('job creation inputs the split field and displays split cards', async () => { + await ml.jobWizardMultiMetric.assertSplitFieldInputExists(); + await ml.jobWizardMultiMetric.selectSplitField(splitField); + + await ml.jobWizardMultiMetric.assertDetectorSplitExists(splitField); + await ml.jobWizardMultiMetric.assertDetectorSplitFrontCardTitle('AAL'); + await ml.jobWizardMultiMetric.assertDetectorSplitNumberOfBackCards(9); + + await ml.jobWizardCommon.assertInfluencerSelection([splitField]); + }); + + it('job creation displays the influencer field', async () => { + await ml.jobWizardCommon.assertInfluencerInputExists(); + await ml.jobWizardCommon.assertInfluencerSelection([splitField]); + }); + + it('job creation inputs the bucket span', async () => { + await ml.jobWizardCommon.assertBucketSpanInputExists(); + await ml.jobWizardCommon.setBucketSpan(bucketSpan); + }); + + it('job creation displays the job details step', async () => { + await ml.jobWizardCommon.advanceToJobDetailsSection(); + }); + + it('job creation inputs the job id', async () => { + await ml.jobWizardCommon.assertJobIdInputExists(); + await ml.jobWizardCommon.setJobId(jobId); + }); + + it('job creation inputs the job description', async () => { + await ml.jobWizardCommon.assertJobDescriptionInputExists(); + await ml.jobWizardCommon.setJobDescription(jobDescription); + }); + + it('job creation inputs job groups', async () => { + await ml.jobWizardCommon.assertJobGroupInputExists(); + for (const jobGroup of jobGroups) { + await ml.jobWizardCommon.addJobGroup(jobGroup); + } + await ml.jobWizardCommon.assertJobGroupSelection(jobGroups); + }); + + it('job creation opens the advanced section', async () => { + await ml.jobWizardCommon.ensureAdvancedSectionOpen(); + }); + + it('job creation displays the model plot switch', async () => { + await ml.jobWizardCommon.assertModelPlotSwitchExists(); + }); + + it('job creation enables the dedicated index switch', async () => { + await ml.jobWizardCommon.assertDedicatedIndexSwitchExists(); + await ml.jobWizardCommon.activateDedicatedIndexSwitch(); + }); + + it('job creation inputs the model memory limit', async () => { + await ml.jobWizardCommon.assertModelMemoryLimitInputExists(); + await ml.jobWizardCommon.setModelMemoryLimit(memoryLimit); + }); + + it('job creation displays the validation step', async () => { + await ml.jobWizardCommon.advanceToValidationSection(); + }); + + it('job creation displays the summary step', async () => { + await ml.jobWizardCommon.advanceToSummarySection(); + }); + + it('job creation creates the job and finishes processing', async () => { + await ml.jobWizardCommon.assertCreateJobButtonExists(); + await ml.jobWizardCommon.createJobAndWaitForCompletion(); + }); + + it('job creation displays the created job in the job list', async () => { + await ml.navigation.navigateToMl(); + await ml.navigation.navigateToJobManagement(); + + await ml.jobTable.waitForJobsToLoad(); + await ml.jobTable.filterWithSearchString(jobId); + const rows = await ml.jobTable.parseJobTable(); + expect(rows.filter(row => row.id === jobId)).to.have.length(1); + }); + + it('job creation displays details for the created job in the job list', async () => { + await ml.jobTable.assertJobRowFields(jobId, getExpectedRow(jobId, jobGroups)); + + await ml.jobTable.assertJobRowDetailsCounts( + jobId, + getExpectedCounts(jobId), + getExpectedModelSizeStats(jobId) + ); + }); + + it('job cloning clicks the clone action and loads the multi metric wizard', async () => { + await ml.jobTable.clickCloneJobAction(jobId); + await ml.jobTypeSelection.assertMultiMetricJobWizardOpen(); + }); + + it('job cloning displays the time range step', async () => { + await ml.jobWizardCommon.assertTimeRangeSectionExists(); + }); + + it('job cloning sets the timerange', async () => { + await ml.jobWizardCommon.clickUseFullDataButton( + 'Feb 7, 2016 @ 00:00:00.000', + 'Feb 11, 2016 @ 23:59:54.000' + ); + }); + + it('job cloning displays the event rate chart', async () => { + await ml.jobWizardCommon.assertEventRateChartExists(); + await ml.jobWizardCommon.assertEventRateChartHasData(); + }); + + it('job cloning displays the pick fields step', async () => { + await ml.jobWizardCommon.advanceToPickFieldsSection(); + }); + + it('job cloning pre-fills detectors and shows preview with split cards', async () => { + for (const [index, aggAndFieldIdentifier] of aggAndFieldIdentifiers.entries()) { + await ml.jobWizardCommon.assertDetectorPreviewExists(aggAndFieldIdentifier, index, 'LINE'); + } + + await ml.jobWizardMultiMetric.assertDetectorSplitExists(splitField); + await ml.jobWizardMultiMetric.assertDetectorSplitFrontCardTitle('AAL'); + await ml.jobWizardMultiMetric.assertDetectorSplitNumberOfBackCards(9); + }); + + it('job cloning pre-fills the split field', async () => { + await ml.jobWizardMultiMetric.assertSplitFieldInputExists(); + await ml.jobWizardMultiMetric.assertSplitFieldSelection(splitField); + }); + + it('job cloning pre-fills influencers', async () => { + await ml.jobWizardCommon.assertInfluencerInputExists(); + await ml.jobWizardCommon.assertInfluencerSelection([splitField]); + }); + + it('job cloning pre-fills the bucket span', async () => { + await ml.jobWizardCommon.assertBucketSpanInputExists(); + await ml.jobWizardCommon.assertBucketSpanValue(bucketSpan); + }); + + it('job cloning displays the job details step', async () => { + await ml.jobWizardCommon.advanceToJobDetailsSection(); + }); + + it('job cloning does not pre-fill the job id', async () => { + await ml.jobWizardCommon.assertJobIdInputExists(); + await ml.jobWizardCommon.assertJobIdValue(''); + }); + + it('job cloning inputs the clone job id', async () => { + await ml.jobWizardCommon.setJobId(jobIdClone); + }); + + it('job cloning pre-fills the job description', async () => { + await ml.jobWizardCommon.assertJobDescriptionInputExists(); + await ml.jobWizardCommon.assertJobDescriptionValue(jobDescription); + }); + + it('job cloning pre-fills job groups', async () => { + await ml.jobWizardCommon.assertJobGroupInputExists(); + await ml.jobWizardCommon.assertJobGroupSelection(jobGroups); + }); + + it('job cloning inputs the clone job group', async () => { + await ml.jobWizardCommon.assertJobGroupInputExists(); + await ml.jobWizardCommon.addJobGroup('clone'); + await ml.jobWizardCommon.assertJobGroupSelection(jobGroupsClone); + }); + + it('job cloning opens the advanced section', async () => { + await ml.jobWizardCommon.ensureAdvancedSectionOpen(); + }); + + it('job cloning pre-fills the model plot switch', async () => { + await ml.jobWizardCommon.assertModelPlotSwitchExists(); + await ml.jobWizardCommon.assertModelPlotSwitchCheckedState(false); + }); + + it('job cloning pre-fills the dedicated index switch', async () => { + await ml.jobWizardCommon.assertDedicatedIndexSwitchExists(); + await ml.jobWizardCommon.assertDedicatedIndexSwitchCheckedState(true); + }); + + it('job cloning pre-fills the model memory limit', async () => { + await ml.jobWizardCommon.assertModelMemoryLimitInputExists(); + await ml.jobWizardCommon.assertModelMemoryLimitValue(memoryLimit); + }); + + it('job cloning displays the validation step', async () => { + await ml.jobWizardCommon.advanceToValidationSection(); + }); + + it('job cloning displays the summary step', async () => { + await ml.jobWizardCommon.advanceToSummarySection(); + }); + + it('job cloning creates the job and finishes processing', async () => { + await ml.jobWizardCommon.assertCreateJobButtonExists(); + await ml.jobWizardCommon.createJobAndWaitForCompletion(); + }); + + it('job cloning displays the created job in the job list', async () => { + await ml.navigation.navigateToMl(); + await ml.navigation.navigateToJobManagement(); + + await ml.jobTable.waitForJobsToLoad(); + await ml.jobTable.filterWithSearchString(jobIdClone); + const rows = await ml.jobTable.parseJobTable(); + expect(rows.filter(row => row.id === jobIdClone)).to.have.length(1); + }); + + it('job cloning displays details for the created job in the job list', async () => { + await ml.jobTable.assertJobRowFields(jobIdClone, getExpectedRow(jobIdClone, jobGroupsClone)); + + await ml.jobTable.assertJobRowDetailsCounts( + jobIdClone, + getExpectedCounts(jobIdClone), + getExpectedModelSizeStats(jobIdClone) + ); }); }); } diff --git a/x-pack/test/functional/apps/machine_learning/anomaly_detection/population_job.ts b/x-pack/test/functional/apps/machine_learning/anomaly_detection/population_job.ts index 706a3c946a261..7ccd9214591f2 100644 --- a/x-pack/test/functional/apps/machine_learning/anomaly_detection/population_job.ts +++ b/x-pack/test/functional/apps/machine_learning/anomaly_detection/population_job.ts @@ -88,7 +88,7 @@ export default function({ getService }: FtrProviderContext) { describe('population', function() { this.tags(['smoke', 'mlqa']); before(async () => { - await esArchiver.loadIfNeeded('ml/ecommerce'); + await esArchiver.load('ml/ecommerce'); }); after(async () => { @@ -96,314 +96,296 @@ export default function({ getService }: FtrProviderContext) { await ml.api.cleanMlIndices(); }); - describe('job creation', function() { - it('loads the job management page', async () => { - await ml.navigation.navigateToMl(); - await ml.navigation.navigateToJobManagement(); - }); + it('job creation loads the job management page', async () => { + await ml.navigation.navigateToMl(); + await ml.navigation.navigateToJobManagement(); + }); - it('loads the new job source selection page', async () => { - await ml.jobManagement.navigateToNewJobSourceSelection(); - }); + it('job creation loads the new job source selection page', async () => { + await ml.jobManagement.navigateToNewJobSourceSelection(); + }); - it('loads the job type selection page', async () => { - await ml.jobSourceSelection.selectSource('ecommerce'); - }); + it('job creation loads the job type selection page', async () => { + await ml.jobSourceSelection.selectSource('ecommerce'); + }); - it('loads the population job wizard page', async () => { - await ml.jobTypeSelection.selectPopulationJob(); - }); + it('job creation loads the population job wizard page', async () => { + await ml.jobTypeSelection.selectPopulationJob(); + }); - it('displays the time range step', async () => { - await ml.jobWizardCommon.assertTimeRangeSectionExists(); - }); + it('job creation displays the time range step', async () => { + await ml.jobWizardCommon.assertTimeRangeSectionExists(); + }); - it('sets the timerange', async () => { - await ml.jobWizardCommon.clickUseFullDataButton( - 'Jun 12, 2019 @ 00:04:19.000', - 'Jul 12, 2019 @ 23:45:36.000' - ); - }); - - it('displays the event rate chart', async () => { - await ml.jobWizardCommon.assertEventRateChartExists(); - await ml.jobWizardCommon.assertEventRateChartHasData(); - }); - - it('displays the pick fields step', async () => { - await ml.jobWizardCommon.advanceToPickFieldsSection(); - }); - - it('selects the population field', async () => { - await ml.jobWizardPopulation.assertPopulationFieldInputExists(); - await ml.jobWizardPopulation.selectPopulationField(populationField); - }); - - it('selects detectors and displays detector previews', async () => { - for (const [index, detector] of detectors.entries()) { - await ml.jobWizardCommon.assertAggAndFieldInputExists(); - await ml.jobWizardCommon.selectAggAndField(detector.identifier, false); - await ml.jobWizardCommon.assertDetectorPreviewExists( - detector.identifier, - index, - 'SCATTER' - ); - } - }); - - it('inputs detector split fields and displays split cards', async () => { - for (const [index, detector] of detectors.entries()) { - await ml.jobWizardPopulation.assertDetectorSplitFieldInputExists(index); - await ml.jobWizardPopulation.selectDetectorSplitField(index, detector.splitField); - - await ml.jobWizardPopulation.assertDetectorSplitExists(index); - await ml.jobWizardPopulation.assertDetectorSplitFrontCardTitle( - index, - detector.frontCardTitle - ); - await ml.jobWizardPopulation.assertDetectorSplitNumberOfBackCards( - index, - detector.numberOfBackCards - ); - } - }); - - it('displays the influencer field', async () => { - await ml.jobWizardCommon.assertInfluencerInputExists(); - await ml.jobWizardCommon.assertInfluencerSelection( - [populationField].concat(detectors.map(detector => detector.splitField)) + it('job creation sets the timerange', async () => { + await ml.jobWizardCommon.clickUseFullDataButton( + 'Jun 12, 2019 @ 00:04:19.000', + 'Jul 12, 2019 @ 23:45:36.000' + ); + }); + + it('job creation displays the event rate chart', async () => { + await ml.jobWizardCommon.assertEventRateChartExists(); + await ml.jobWizardCommon.assertEventRateChartHasData(); + }); + + it('job creation displays the pick fields step', async () => { + await ml.jobWizardCommon.advanceToPickFieldsSection(); + }); + + it('job creation selects the population field', async () => { + await ml.jobWizardPopulation.assertPopulationFieldInputExists(); + await ml.jobWizardPopulation.selectPopulationField(populationField); + }); + + it('job creation selects detectors and displays detector previews', async () => { + for (const [index, detector] of detectors.entries()) { + await ml.jobWizardCommon.assertAggAndFieldInputExists(); + await ml.jobWizardCommon.selectAggAndField(detector.identifier, false); + await ml.jobWizardCommon.assertDetectorPreviewExists(detector.identifier, index, 'SCATTER'); + } + }); + + it('job creation inputs detector split fields and displays split cards', async () => { + for (const [index, detector] of detectors.entries()) { + await ml.jobWizardPopulation.assertDetectorSplitFieldInputExists(index); + await ml.jobWizardPopulation.selectDetectorSplitField(index, detector.splitField); + + await ml.jobWizardPopulation.assertDetectorSplitExists(index); + await ml.jobWizardPopulation.assertDetectorSplitFrontCardTitle( + index, + detector.frontCardTitle ); - }); - - it('inputs the bucket span', async () => { - await ml.jobWizardCommon.assertBucketSpanInputExists(); - await ml.jobWizardCommon.setBucketSpan(bucketSpan); - }); - - it('displays the job details step', async () => { - await ml.jobWizardCommon.advanceToJobDetailsSection(); - }); - - it('inputs the job id', async () => { - await ml.jobWizardCommon.assertJobIdInputExists(); - await ml.jobWizardCommon.setJobId(jobId); - }); - - it('inputs the job description', async () => { - await ml.jobWizardCommon.assertJobDescriptionInputExists(); - await ml.jobWizardCommon.setJobDescription(jobDescription); - }); - - it('inputs job groups', async () => { - await ml.jobWizardCommon.assertJobGroupInputExists(); - for (const jobGroup of jobGroups) { - await ml.jobWizardCommon.addJobGroup(jobGroup); - } - await ml.jobWizardCommon.assertJobGroupSelection(jobGroups); - }); - - it('opens the advanced section', async () => { - await ml.jobWizardCommon.ensureAdvancedSectionOpen(); - }); - - it('displays the model plot switch', async () => { - await ml.jobWizardCommon.assertModelPlotSwitchExists(); - }); - - it('enables the dedicated index switch', async () => { - await ml.jobWizardCommon.assertDedicatedIndexSwitchExists(); - await ml.jobWizardCommon.activateDedicatedIndexSwitch(); - }); - - it('inputs the model memory limit', async () => { - await ml.jobWizardCommon.assertModelMemoryLimitInputExists(); - await ml.jobWizardCommon.setModelMemoryLimit(memoryLimit); - }); - - it('displays the validation step', async () => { - await ml.jobWizardCommon.advanceToValidationSection(); - }); - - it('displays the summary step', async () => { - await ml.jobWizardCommon.advanceToSummarySection(); - }); - - it('creates the job and finishes processing', async () => { - await ml.jobWizardCommon.assertCreateJobButtonExists(); - await ml.jobWizardCommon.createJobAndWaitForCompletion(); - }); - - it('displays the created job in the job list', async () => { - await ml.navigation.navigateToMl(); - await ml.navigation.navigateToJobManagement(); - - await ml.jobTable.waitForJobsToLoad(); - await ml.jobTable.filterWithSearchString(jobId); - const rows = await ml.jobTable.parseJobTable(); - expect(rows.filter(row => row.id === jobId)).to.have.length(1); - }); - - it('displays details for the created job in the job list', async () => { - await ml.jobTable.assertJobRowFields(jobId, getExpectedRow(jobId, jobGroups)); - - await ml.jobTable.assertJobRowDetailsCounts( - jobId, - getExpectedCounts(jobId), - getExpectedModelSizeStats(jobId) + await ml.jobWizardPopulation.assertDetectorSplitNumberOfBackCards( + index, + detector.numberOfBackCards ); - }); + } + }); + + it('job creation displays the influencer field', async () => { + await ml.jobWizardCommon.assertInfluencerInputExists(); + await ml.jobWizardCommon.assertInfluencerSelection( + [populationField].concat(detectors.map(detector => detector.splitField)) + ); }); - describe('job cloning', function() { - it('clicks the clone action and loads the population wizard', async () => { - await ml.jobTable.clickCloneJobAction(jobId); - await ml.jobTypeSelection.assertPopulationJobWizardOpen(); - }); + it('job creation inputs the bucket span', async () => { + await ml.jobWizardCommon.assertBucketSpanInputExists(); + await ml.jobWizardCommon.setBucketSpan(bucketSpan); + }); - it('displays the time range step', async () => { - await ml.jobWizardCommon.assertTimeRangeSectionExists(); - }); + it('job creation displays the job details step', async () => { + await ml.jobWizardCommon.advanceToJobDetailsSection(); + }); - it('sets the timerange', async () => { - await ml.jobWizardCommon.clickUseFullDataButton( - 'Jun 12, 2019 @ 00:04:19.000', - 'Jul 12, 2019 @ 23:45:36.000' - ); - }); - - it('displays the event rate chart', async () => { - await ml.jobWizardCommon.assertEventRateChartExists(); - await ml.jobWizardCommon.assertEventRateChartHasData(); - }); - - it('displays the pick fields step', async () => { - await ml.jobWizardCommon.advanceToPickFieldsSection(); - }); - - it('pre-fills the population field', async () => { - await ml.jobWizardPopulation.assertPopulationFieldInputExists(); - await ml.jobWizardPopulation.assertPopulationFieldSelection(populationField); - }); - - it('pre-fills detectors and shows preview with split cards', async () => { - for (const [index, detector] of detectors.entries()) { - await ml.jobWizardCommon.assertDetectorPreviewExists( - detector.identifier, - index, - 'SCATTER' - ); - - await ml.jobWizardPopulation.assertDetectorSplitFieldSelection( - index, - detector.splitField - ); - await ml.jobWizardPopulation.assertDetectorSplitExists(index); - await ml.jobWizardPopulation.assertDetectorSplitFrontCardTitle( - index, - detector.frontCardTitle - ); - await ml.jobWizardPopulation.assertDetectorSplitNumberOfBackCards( - index, - detector.numberOfBackCards - ); - } - }); - - it('pre-fills influencers', async () => { - await ml.jobWizardCommon.assertInfluencerInputExists(); - await ml.jobWizardCommon.assertInfluencerSelection( - [populationField].concat(detectors.map(detector => detector.splitField)) + it('job creation inputs the job id', async () => { + await ml.jobWizardCommon.assertJobIdInputExists(); + await ml.jobWizardCommon.setJobId(jobId); + }); + + it('job creation inputs the job description', async () => { + await ml.jobWizardCommon.assertJobDescriptionInputExists(); + await ml.jobWizardCommon.setJobDescription(jobDescription); + }); + + it('job creation inputs job groups', async () => { + await ml.jobWizardCommon.assertJobGroupInputExists(); + for (const jobGroup of jobGroups) { + await ml.jobWizardCommon.addJobGroup(jobGroup); + } + await ml.jobWizardCommon.assertJobGroupSelection(jobGroups); + }); + + it('job creation opens the advanced section', async () => { + await ml.jobWizardCommon.ensureAdvancedSectionOpen(); + }); + + it('job creation displays the model plot switch', async () => { + await ml.jobWizardCommon.assertModelPlotSwitchExists(); + }); + + it('job creation enables the dedicated index switch', async () => { + await ml.jobWizardCommon.assertDedicatedIndexSwitchExists(); + await ml.jobWizardCommon.activateDedicatedIndexSwitch(); + }); + + it('job creation inputs the model memory limit', async () => { + await ml.jobWizardCommon.assertModelMemoryLimitInputExists(); + await ml.jobWizardCommon.setModelMemoryLimit(memoryLimit); + }); + + it('job creation displays the validation step', async () => { + await ml.jobWizardCommon.advanceToValidationSection(); + }); + + it('job creation displays the summary step', async () => { + await ml.jobWizardCommon.advanceToSummarySection(); + }); + + it('job creation creates the job and finishes processing', async () => { + await ml.jobWizardCommon.assertCreateJobButtonExists(); + await ml.jobWizardCommon.createJobAndWaitForCompletion(); + }); + + it('job creation displays the created job in the job list', async () => { + await ml.navigation.navigateToMl(); + await ml.navigation.navigateToJobManagement(); + + await ml.jobTable.waitForJobsToLoad(); + await ml.jobTable.filterWithSearchString(jobId); + const rows = await ml.jobTable.parseJobTable(); + expect(rows.filter(row => row.id === jobId)).to.have.length(1); + }); + + it('job creation displays details for the created job in the job list', async () => { + await ml.jobTable.assertJobRowFields(jobId, getExpectedRow(jobId, jobGroups)); + + await ml.jobTable.assertJobRowDetailsCounts( + jobId, + getExpectedCounts(jobId), + getExpectedModelSizeStats(jobId) + ); + }); + + it('job cloning clicks the clone action and loads the population wizard', async () => { + await ml.jobTable.clickCloneJobAction(jobId); + await ml.jobTypeSelection.assertPopulationJobWizardOpen(); + }); + + it('job cloning displays the time range step', async () => { + await ml.jobWizardCommon.assertTimeRangeSectionExists(); + }); + + it('job cloning sets the timerange', async () => { + await ml.jobWizardCommon.clickUseFullDataButton( + 'Jun 12, 2019 @ 00:04:19.000', + 'Jul 12, 2019 @ 23:45:36.000' + ); + }); + + it('job cloning displays the event rate chart', async () => { + await ml.jobWizardCommon.assertEventRateChartExists(); + await ml.jobWizardCommon.assertEventRateChartHasData(); + }); + + it('job cloning displays the pick fields step', async () => { + await ml.jobWizardCommon.advanceToPickFieldsSection(); + }); + + it('job cloning pre-fills the population field', async () => { + await ml.jobWizardPopulation.assertPopulationFieldInputExists(); + await ml.jobWizardPopulation.assertPopulationFieldSelection(populationField); + }); + + it('job cloning pre-fills detectors and shows preview with split cards', async () => { + for (const [index, detector] of detectors.entries()) { + await ml.jobWizardCommon.assertDetectorPreviewExists(detector.identifier, index, 'SCATTER'); + + await ml.jobWizardPopulation.assertDetectorSplitFieldSelection(index, detector.splitField); + await ml.jobWizardPopulation.assertDetectorSplitExists(index); + await ml.jobWizardPopulation.assertDetectorSplitFrontCardTitle( + index, + detector.frontCardTitle ); - }); - - it('pre-fills the bucket span', async () => { - await ml.jobWizardCommon.assertBucketSpanInputExists(); - await ml.jobWizardCommon.assertBucketSpanValue(bucketSpan); - }); - - it('displays the job details step', async () => { - await ml.jobWizardCommon.advanceToJobDetailsSection(); - }); - - it('does not pre-fill the job id', async () => { - await ml.jobWizardCommon.assertJobIdInputExists(); - await ml.jobWizardCommon.assertJobIdValue(''); - }); - - it('inputs the clone job id', async () => { - await ml.jobWizardCommon.setJobId(jobIdClone); - }); - - it('pre-fills the job description', async () => { - await ml.jobWizardCommon.assertJobDescriptionInputExists(); - await ml.jobWizardCommon.assertJobDescriptionValue(jobDescription); - }); - - it('pre-fills job groups', async () => { - await ml.jobWizardCommon.assertJobGroupInputExists(); - await ml.jobWizardCommon.assertJobGroupSelection(jobGroups); - }); - - it('inputs the clone job group', async () => { - await ml.jobWizardCommon.assertJobGroupInputExists(); - await ml.jobWizardCommon.addJobGroup('clone'); - await ml.jobWizardCommon.assertJobGroupSelection(jobGroupsClone); - }); - - it('opens the advanced section', async () => { - await ml.jobWizardCommon.ensureAdvancedSectionOpen(); - }); - - it('pre-fills the model plot switch', async () => { - await ml.jobWizardCommon.assertModelPlotSwitchExists(); - await ml.jobWizardCommon.assertModelPlotSwitchCheckedState(false); - }); - - it('pre-fills the dedicated index switch', async () => { - await ml.jobWizardCommon.assertDedicatedIndexSwitchExists(); - await ml.jobWizardCommon.assertDedicatedIndexSwitchCheckedState(true); - }); - - it('pre-fills the model memory limit', async () => { - await ml.jobWizardCommon.assertModelMemoryLimitInputExists(); - await ml.jobWizardCommon.assertModelMemoryLimitValue(memoryLimit); - }); - - it('displays the validation step', async () => { - await ml.jobWizardCommon.advanceToValidationSection(); - }); - - it('displays the summary step', async () => { - await ml.jobWizardCommon.advanceToSummarySection(); - }); - - it('creates the job and finishes processing', async () => { - await ml.jobWizardCommon.assertCreateJobButtonExists(); - await ml.jobWizardCommon.createJobAndWaitForCompletion(); - }); - - it('displays the created job in the job list', async () => { - await ml.navigation.navigateToMl(); - await ml.navigation.navigateToJobManagement(); - - await ml.jobTable.waitForJobsToLoad(); - await ml.jobTable.filterWithSearchString(jobIdClone); - const rows = await ml.jobTable.parseJobTable(); - expect(rows.filter(row => row.id === jobIdClone)).to.have.length(1); - }); - - it('displays details for the created job in the job list', async () => { - await ml.jobTable.assertJobRowFields( - jobIdClone, - getExpectedRow(jobIdClone, jobGroupsClone) + await ml.jobWizardPopulation.assertDetectorSplitNumberOfBackCards( + index, + detector.numberOfBackCards ); + } + }); - await ml.jobTable.assertJobRowDetailsCounts( - jobIdClone, - getExpectedCounts(jobIdClone), - getExpectedModelSizeStats(jobIdClone) - ); - }); + it('job cloning pre-fills influencers', async () => { + await ml.jobWizardCommon.assertInfluencerInputExists(); + await ml.jobWizardCommon.assertInfluencerSelection( + [populationField].concat(detectors.map(detector => detector.splitField)) + ); + }); + + it('job cloning pre-fills the bucket span', async () => { + await ml.jobWizardCommon.assertBucketSpanInputExists(); + await ml.jobWizardCommon.assertBucketSpanValue(bucketSpan); + }); + + it('job cloning displays the job details step', async () => { + await ml.jobWizardCommon.advanceToJobDetailsSection(); + }); + + it('job cloning does not pre-fill the job id', async () => { + await ml.jobWizardCommon.assertJobIdInputExists(); + await ml.jobWizardCommon.assertJobIdValue(''); + }); + + it('job cloning inputs the clone job id', async () => { + await ml.jobWizardCommon.setJobId(jobIdClone); + }); + + it('job cloning pre-fills the job description', async () => { + await ml.jobWizardCommon.assertJobDescriptionInputExists(); + await ml.jobWizardCommon.assertJobDescriptionValue(jobDescription); + }); + + it('job cloning pre-fills job groups', async () => { + await ml.jobWizardCommon.assertJobGroupInputExists(); + await ml.jobWizardCommon.assertJobGroupSelection(jobGroups); + }); + + it('job cloning inputs the clone job group', async () => { + await ml.jobWizardCommon.assertJobGroupInputExists(); + await ml.jobWizardCommon.addJobGroup('clone'); + await ml.jobWizardCommon.assertJobGroupSelection(jobGroupsClone); + }); + + it('job cloning opens the advanced section', async () => { + await ml.jobWizardCommon.ensureAdvancedSectionOpen(); + }); + + it('job cloning pre-fills the model plot switch', async () => { + await ml.jobWizardCommon.assertModelPlotSwitchExists(); + await ml.jobWizardCommon.assertModelPlotSwitchCheckedState(false); + }); + + it('job cloning pre-fills the dedicated index switch', async () => { + await ml.jobWizardCommon.assertDedicatedIndexSwitchExists(); + await ml.jobWizardCommon.assertDedicatedIndexSwitchCheckedState(true); + }); + + it('job cloning pre-fills the model memory limit', async () => { + await ml.jobWizardCommon.assertModelMemoryLimitInputExists(); + await ml.jobWizardCommon.assertModelMemoryLimitValue(memoryLimit); + }); + + it('job cloning displays the validation step', async () => { + await ml.jobWizardCommon.advanceToValidationSection(); + }); + + it('job cloning displays the summary step', async () => { + await ml.jobWizardCommon.advanceToSummarySection(); + }); + + it('job cloning creates the job and finishes processing', async () => { + await ml.jobWizardCommon.assertCreateJobButtonExists(); + await ml.jobWizardCommon.createJobAndWaitForCompletion(); + }); + + it('job cloning displays the created job in the job list', async () => { + await ml.navigation.navigateToMl(); + await ml.navigation.navigateToJobManagement(); + + await ml.jobTable.waitForJobsToLoad(); + await ml.jobTable.filterWithSearchString(jobIdClone); + const rows = await ml.jobTable.parseJobTable(); + expect(rows.filter(row => row.id === jobIdClone)).to.have.length(1); + }); + + it('job cloning displays details for the created job in the job list', async () => { + await ml.jobTable.assertJobRowFields(jobIdClone, getExpectedRow(jobIdClone, jobGroupsClone)); + + await ml.jobTable.assertJobRowDetailsCounts( + jobIdClone, + getExpectedCounts(jobIdClone), + getExpectedModelSizeStats(jobIdClone) + ); }); }); } diff --git a/x-pack/test/functional/apps/machine_learning/anomaly_detection/saved_search_job.ts b/x-pack/test/functional/apps/machine_learning/anomaly_detection/saved_search_job.ts index 9591cdd3ff561..5645bc7277d19 100644 --- a/x-pack/test/functional/apps/machine_learning/anomaly_detection/saved_search_job.ts +++ b/x-pack/test/functional/apps/machine_learning/anomaly_detection/saved_search_job.ts @@ -274,7 +274,7 @@ export default function({ getService }: FtrProviderContext) { describe('saved search', function() { this.tags(['smoke', 'mlqa']); before(async () => { - await esArchiver.loadIfNeeded('ml/farequote'); + await esArchiver.load('ml/farequote'); }); after(async () => { @@ -283,45 +283,45 @@ export default function({ getService }: FtrProviderContext) { }); for (const testData of testDataList) { - describe(`job creation ${testData.suiteTitle}`, function() { - it('loads the job management page', async () => { + describe(` ${testData.suiteTitle}`, function() { + it('job creation loads the job management page', async () => { await ml.navigation.navigateToMl(); await ml.navigation.navigateToJobManagement(); }); - it('loads the new job source selection page', async () => { + it('job creation loads the new job source selection page', async () => { await ml.jobManagement.navigateToNewJobSourceSelection(); }); - it('loads the job type selection page', async () => { + it('job creation loads the job type selection page', async () => { await ml.jobSourceSelection.selectSource(testData.jobSource); }); - it('loads the multi metric job wizard page', async () => { + it('job creation loads the multi metric job wizard page', async () => { await ml.jobTypeSelection.selectMultiMetricJob(); }); - it('displays the time range step', async () => { + it('job creation displays the time range step', async () => { await ml.jobWizardCommon.assertTimeRangeSectionExists(); }); - it('sets the timerange', async () => { + it('job creation sets the timerange', async () => { await ml.jobWizardCommon.clickUseFullDataButton( 'Feb 7, 2016 @ 00:00:00.000', 'Feb 11, 2016 @ 23:59:54.000' ); }); - it('displays the event rate chart', async () => { + it('job creation displays the event rate chart', async () => { await ml.jobWizardCommon.assertEventRateChartExists(); await ml.jobWizardCommon.assertEventRateChartHasData(); }); - it('displays the pick fields step', async () => { + it('job creation displays the pick fields step', async () => { await ml.jobWizardCommon.advanceToPickFieldsSection(); }); - it('selects detectors and displays detector previews', async () => { + it('job creation selects detectors and displays detector previews', async () => { for (const [index, aggAndFieldIdentifier] of testData.aggAndFieldIdentifiers.entries()) { await ml.jobWizardCommon.assertAggAndFieldInputExists(); await ml.jobWizardCommon.selectAggAndField(aggAndFieldIdentifier, false); @@ -333,7 +333,7 @@ export default function({ getService }: FtrProviderContext) { } }); - it('inputs the split field and displays split cards', async () => { + it('job creation inputs the split field and displays split cards', async () => { await ml.jobWizardMultiMetric.assertSplitFieldInputExists(); await ml.jobWizardMultiMetric.selectSplitField(testData.splitField); @@ -348,31 +348,31 @@ export default function({ getService }: FtrProviderContext) { await ml.jobWizardCommon.assertInfluencerSelection([testData.splitField]); }); - it('displays the influencer field', async () => { + it('job creation displays the influencer field', async () => { await ml.jobWizardCommon.assertInfluencerInputExists(); await ml.jobWizardCommon.assertInfluencerSelection([testData.splitField]); }); - it('inputs the bucket span', async () => { + it('job creation inputs the bucket span', async () => { await ml.jobWizardCommon.assertBucketSpanInputExists(); await ml.jobWizardCommon.setBucketSpan(testData.bucketSpan); }); - it('displays the job details step', async () => { + it('job creation displays the job details step', async () => { await ml.jobWizardCommon.advanceToJobDetailsSection(); }); - it('inputs the job id', async () => { + it('job creation inputs the job id', async () => { await ml.jobWizardCommon.assertJobIdInputExists(); await ml.jobWizardCommon.setJobId(testData.jobId); }); - it('inputs the job description', async () => { + it('job creation inputs the job description', async () => { await ml.jobWizardCommon.assertJobDescriptionInputExists(); await ml.jobWizardCommon.setJobDescription(testData.jobDescription); }); - it('inputs job groups', async () => { + it('job creation inputs job groups', async () => { await ml.jobWizardCommon.assertJobGroupInputExists(); for (const jobGroup of testData.jobGroups) { await ml.jobWizardCommon.addJobGroup(jobGroup); @@ -380,38 +380,38 @@ export default function({ getService }: FtrProviderContext) { await ml.jobWizardCommon.assertJobGroupSelection(testData.jobGroups); }); - it('opens the advanced section', async () => { + it('job creation opens the advanced section', async () => { await ml.jobWizardCommon.ensureAdvancedSectionOpen(); }); - it('displays the model plot switch', async () => { + it('job creation displays the model plot switch', async () => { await ml.jobWizardCommon.assertModelPlotSwitchExists(); }); - it('enables the dedicated index switch', async () => { + it('job creation enables the dedicated index switch', async () => { await ml.jobWizardCommon.assertDedicatedIndexSwitchExists(); await ml.jobWizardCommon.activateDedicatedIndexSwitch(); }); - it('inputs the model memory limit', async () => { + it('job creation inputs the model memory limit', async () => { await ml.jobWizardCommon.assertModelMemoryLimitInputExists(); await ml.jobWizardCommon.setModelMemoryLimit(testData.memoryLimit); }); - it('displays the validation step', async () => { + it('job creation displays the validation step', async () => { await ml.jobWizardCommon.advanceToValidationSection(); }); - it('displays the summary step', async () => { + it('job creation displays the summary step', async () => { await ml.jobWizardCommon.advanceToSummarySection(); }); - it('creates the job and finishes processing', async () => { + it('job creation creates the job and finishes processing', async () => { await ml.jobWizardCommon.assertCreateJobButtonExists(); await ml.jobWizardCommon.createJobAndWaitForCompletion(); }); - it('displays the created job in the job list', async () => { + it('job creation displays the created job in the job list', async () => { await ml.navigation.navigateToMl(); await ml.navigation.navigateToJobManagement(); @@ -421,7 +421,7 @@ export default function({ getService }: FtrProviderContext) { expect(rows.filter(row => row.id === testData.jobId)).to.have.length(1); }); - it('displays details for the created job in the job list', async () => { + it('job creation displays details for the created job in the job list', async () => { await ml.jobTable.assertJobRowFields(testData.jobId, { id: testData.jobId, description: testData.jobDescription, diff --git a/x-pack/test/functional/apps/machine_learning/anomaly_detection/single_metric_job.ts b/x-pack/test/functional/apps/machine_learning/anomaly_detection/single_metric_job.ts index b5b3d2371bf1a..06ec840b36aae 100644 --- a/x-pack/test/functional/apps/machine_learning/anomaly_detection/single_metric_job.ts +++ b/x-pack/test/functional/apps/machine_learning/anomaly_detection/single_metric_job.ts @@ -73,7 +73,7 @@ export default function({ getService }: FtrProviderContext) { describe('single metric', function() { this.tags(['smoke', 'mlqa']); before(async () => { - await esArchiver.loadIfNeeded('ml/farequote'); + await esArchiver.load('ml/farequote'); }); after(async () => { @@ -81,274 +81,265 @@ export default function({ getService }: FtrProviderContext) { await ml.api.cleanMlIndices(); }); - describe('job creation', function() { - it('loads the job management page', async () => { - await ml.navigation.navigateToMl(); - await ml.navigation.navigateToJobManagement(); - }); - - it('loads the new job source selection page', async () => { - await ml.jobManagement.navigateToNewJobSourceSelection(); - }); - - it('loads the job type selection page', async () => { - await ml.jobSourceSelection.selectSource('farequote'); - }); - - it('loads the single metric job wizard page', async () => { - await ml.jobTypeSelection.selectSingleMetricJob(); - }); - - it('displays the time range step', async () => { - await ml.jobWizardCommon.assertTimeRangeSectionExists(); - }); - - it('sets the timerange', async () => { - await ml.jobWizardCommon.clickUseFullDataButton( - 'Feb 7, 2016 @ 00:00:00.000', - 'Feb 11, 2016 @ 23:59:54.000' - ); - }); - - it('displays the event rate chart', async () => { - await ml.jobWizardCommon.assertEventRateChartExists(); - await ml.jobWizardCommon.assertEventRateChartHasData(); - }); - - it('displays the pick fields step', async () => { - await ml.jobWizardCommon.advanceToPickFieldsSection(); - }); - - it('selects field and aggregation', async () => { - await ml.jobWizardCommon.assertAggAndFieldInputExists(); - await ml.jobWizardCommon.selectAggAndField(aggAndFieldIdentifier, true); - await ml.jobWizardCommon.assertAnomalyChartExists('LINE'); - }); - - it('inputs the bucket span', async () => { - await ml.jobWizardCommon.assertBucketSpanInputExists(); - await ml.jobWizardCommon.setBucketSpan(bucketSpan); - }); - - it('displays the job details step', async () => { - await ml.jobWizardCommon.advanceToJobDetailsSection(); - }); - - it('inputs the job id', async () => { - await ml.jobWizardCommon.assertJobIdInputExists(); - await ml.jobWizardCommon.setJobId(jobId); - }); - - it('inputs the job description', async () => { - await ml.jobWizardCommon.assertJobDescriptionInputExists(); - await ml.jobWizardCommon.setJobDescription(jobDescription); - }); - - it('inputs job groups', async () => { - await ml.jobWizardCommon.assertJobGroupInputExists(); - for (const jobGroup of jobGroups) { - await ml.jobWizardCommon.addJobGroup(jobGroup); - } - await ml.jobWizardCommon.assertJobGroupSelection(jobGroups); - }); - - it('opens the advanced section', async () => { - await ml.jobWizardCommon.ensureAdvancedSectionOpen(); - }); - - it('displays the model plot switch', async () => { - await ml.jobWizardCommon.assertModelPlotSwitchExists(); - }); - - it('enables the dedicated index switch', async () => { - await ml.jobWizardCommon.assertDedicatedIndexSwitchExists(); - await ml.jobWizardCommon.activateDedicatedIndexSwitch(); - }); - - it('inputs the model memory limit', async () => { - await ml.jobWizardCommon.assertModelMemoryLimitInputExists(); - await ml.jobWizardCommon.setModelMemoryLimit(memoryLimit); - }); - - it('displays the validation step', async () => { - await ml.jobWizardCommon.advanceToValidationSection(); - }); - - it('displays the summary step', async () => { - await ml.jobWizardCommon.advanceToSummarySection(); - }); - - it('creates the job and finishes processing', async () => { - await ml.jobWizardCommon.assertCreateJobButtonExists(); - await ml.jobWizardCommon.createJobAndWaitForCompletion(); - }); - - it('displays the created job in the job list', async () => { - await ml.navigation.navigateToMl(); - await ml.navigation.navigateToJobManagement(); - - await ml.jobTable.waitForJobsToLoad(); - await ml.jobTable.filterWithSearchString(jobId); - const rows = await ml.jobTable.parseJobTable(); - expect(rows.filter(row => row.id === jobId)).to.have.length(1); - }); - - it('displays details for the created job in the job list', async () => { - await ml.jobTable.assertJobRowFields(jobId, getExpectedRow(jobId, jobGroups)); - - await ml.jobTable.assertJobRowDetailsCounts( - jobId, - getExpectedCounts(jobId), - getExpectedModelSizeStats(jobId) - ); - }); - }); - - describe('job cloning', function() { - it('clicks the clone action and loads the single metric wizard', async () => { - await ml.jobTable.clickCloneJobAction(jobId); - await ml.jobTypeSelection.assertSingleMetricJobWizardOpen(); - }); - - it('displays the time range step', async () => { - await ml.jobWizardCommon.assertTimeRangeSectionExists(); - }); - - it('sets the timerange', async () => { - await ml.jobWizardCommon.clickUseFullDataButton( - 'Feb 7, 2016 @ 00:00:00.000', - 'Feb 11, 2016 @ 23:59:54.000' - ); - }); - - it('displays the event rate chart', async () => { - await ml.jobWizardCommon.assertEventRateChartExists(); - await ml.jobWizardCommon.assertEventRateChartHasData(); - }); - - it('displays the pick fields step', async () => { - await ml.jobWizardCommon.advanceToPickFieldsSection(); - }); - - it('pre-fills field and aggregation', async () => { - await ml.jobWizardCommon.assertAggAndFieldInputExists(); - await ml.jobWizardCommon.assertAggAndFieldSelection(aggAndFieldIdentifier); - await ml.jobWizardCommon.assertAnomalyChartExists('LINE'); - }); - - it('pre-fills the bucket span', async () => { - await ml.jobWizardCommon.assertBucketSpanInputExists(); - await ml.jobWizardCommon.assertBucketSpanValue(bucketSpan); - }); - - it('displays the job details step', async () => { - await ml.jobWizardCommon.advanceToJobDetailsSection(); - }); - - it('does not pre-fill the job id', async () => { - await ml.jobWizardCommon.assertJobIdInputExists(); - await ml.jobWizardCommon.assertJobIdValue(''); - }); - - it('inputs the clone job id', async () => { - await ml.jobWizardCommon.setJobId(jobIdClone); - }); - - it('pre-fills the job description', async () => { - await ml.jobWizardCommon.assertJobDescriptionInputExists(); - await ml.jobWizardCommon.assertJobDescriptionValue(jobDescription); - }); - - it('pre-fills job groups', async () => { - await ml.jobWizardCommon.assertJobGroupInputExists(); - await ml.jobWizardCommon.assertJobGroupSelection(jobGroups); - }); - - it('inputs the clone job group', async () => { - await ml.jobWizardCommon.assertJobGroupInputExists(); - await ml.jobWizardCommon.addJobGroup('clone'); - await ml.jobWizardCommon.assertJobGroupSelection(jobGroupsClone); - }); - - it('opens the advanced section', async () => { - await ml.jobWizardCommon.ensureAdvancedSectionOpen(); - }); - - it('pre-fills the model plot switch', async () => { - await ml.jobWizardCommon.assertModelPlotSwitchExists(); - await ml.jobWizardCommon.assertModelPlotSwitchCheckedState(true); - }); - - it('pre-fills the dedicated index switch', async () => { - await ml.jobWizardCommon.assertDedicatedIndexSwitchExists(); - await ml.jobWizardCommon.assertDedicatedIndexSwitchCheckedState(true); - }); - - it('pre-fills the model memory limit', async () => { - await ml.jobWizardCommon.assertModelMemoryLimitInputExists(); - await ml.jobWizardCommon.assertModelMemoryLimitValue(memoryLimit); - }); - - it('displays the validation step', async () => { - await ml.jobWizardCommon.advanceToValidationSection(); - }); - - it('displays the summary step', async () => { - await ml.jobWizardCommon.advanceToSummarySection(); - }); - - it('creates the job and finishes processing', async () => { - await ml.jobWizardCommon.assertCreateJobButtonExists(); - await ml.jobWizardCommon.createJobAndWaitForCompletion(); - }); - - it('displays the created job in the job list', async () => { - await ml.navigation.navigateToMl(); - await ml.navigation.navigateToJobManagement(); - - await ml.jobTable.waitForJobsToLoad(); - await ml.jobTable.filterWithSearchString(jobIdClone); - const rows = await ml.jobTable.parseJobTable(); - expect(rows.filter(row => row.id === jobIdClone)).to.have.length(1); - }); - - it('displays details for the created job in the job list', async () => { - await ml.jobTable.assertJobRowFields( - jobIdClone, - getExpectedRow(jobIdClone, jobGroupsClone) - ); - - await ml.jobTable.assertJobRowDetailsCounts( - jobIdClone, - getExpectedCounts(jobIdClone), - getExpectedModelSizeStats(jobIdClone) - ); - }); - }); - - describe('job deletion', function() { - it('has results for the job before deletion', async () => { - await ml.api.assertJobResultsExist(jobIdClone); - }); - - it('triggers the delete action', async () => { - await ml.jobTable.clickDeleteJobAction(jobIdClone); - }); - - it('confirms the delete modal', async () => { - await ml.jobTable.confirmDeleteJobModal(); - }); - - it('does not display the deleted job in the job list any more', async () => { - await ml.jobTable.waitForJobsToLoad(); - await ml.jobTable.filterWithSearchString(jobIdClone); - const rows = await ml.jobTable.parseJobTable(); - expect(rows.filter(row => row.id === jobIdClone)).to.have.length(0); - }); - - it('does not have results for the deleted job any more', async () => { - await ml.api.assertNoJobResultsExist(jobIdClone); - }); + it('job creation loads the job management page', async () => { + await ml.navigation.navigateToMl(); + await ml.navigation.navigateToJobManagement(); + }); + + it('job creation loads the new job source selection page', async () => { + await ml.jobManagement.navigateToNewJobSourceSelection(); + }); + + it('job creation loads the job type selection page', async () => { + await ml.jobSourceSelection.selectSource('farequote'); + }); + + it('job creation loads the single metric job wizard page', async () => { + await ml.jobTypeSelection.selectSingleMetricJob(); + }); + + it('job creation displays the time range step', async () => { + await ml.jobWizardCommon.assertTimeRangeSectionExists(); + }); + + it('job creation sets the timerange', async () => { + await ml.jobWizardCommon.clickUseFullDataButton( + 'Feb 7, 2016 @ 00:00:00.000', + 'Feb 11, 2016 @ 23:59:54.000' + ); + }); + + it('job creation displays the event rate chart', async () => { + await ml.jobWizardCommon.assertEventRateChartExists(); + await ml.jobWizardCommon.assertEventRateChartHasData(); + }); + + it('job creation displays the pick fields step', async () => { + await ml.jobWizardCommon.advanceToPickFieldsSection(); + }); + + it('job creation selects field and aggregation', async () => { + await ml.jobWizardCommon.assertAggAndFieldInputExists(); + await ml.jobWizardCommon.selectAggAndField(aggAndFieldIdentifier, true); + await ml.jobWizardCommon.assertAnomalyChartExists('LINE'); + }); + + it('job creation inputs the bucket span', async () => { + await ml.jobWizardCommon.assertBucketSpanInputExists(); + await ml.jobWizardCommon.setBucketSpan(bucketSpan); + }); + + it('job creation displays the job details step', async () => { + await ml.jobWizardCommon.advanceToJobDetailsSection(); + }); + + it('job creation inputs the job id', async () => { + await ml.jobWizardCommon.assertJobIdInputExists(); + await ml.jobWizardCommon.setJobId(jobId); + }); + + it('job creation inputs the job description', async () => { + await ml.jobWizardCommon.assertJobDescriptionInputExists(); + await ml.jobWizardCommon.setJobDescription(jobDescription); + }); + + it('job creation inputs job groups', async () => { + await ml.jobWizardCommon.assertJobGroupInputExists(); + for (const jobGroup of jobGroups) { + await ml.jobWizardCommon.addJobGroup(jobGroup); + } + await ml.jobWizardCommon.assertJobGroupSelection(jobGroups); + }); + + it('job creation opens the advanced section', async () => { + await ml.jobWizardCommon.ensureAdvancedSectionOpen(); + }); + + it('job creation displays the model plot switch', async () => { + await ml.jobWizardCommon.assertModelPlotSwitchExists(); + }); + + it('job creation enables the dedicated index switch', async () => { + await ml.jobWizardCommon.assertDedicatedIndexSwitchExists(); + await ml.jobWizardCommon.activateDedicatedIndexSwitch(); + }); + + it('job creation inputs the model memory limit', async () => { + await ml.jobWizardCommon.assertModelMemoryLimitInputExists(); + await ml.jobWizardCommon.setModelMemoryLimit(memoryLimit); + }); + + it('job creation displays the validation step', async () => { + await ml.jobWizardCommon.advanceToValidationSection(); + }); + + it('job creation displays the summary step', async () => { + await ml.jobWizardCommon.advanceToSummarySection(); + }); + + it('job creation creates the job and finishes processing', async () => { + await ml.jobWizardCommon.assertCreateJobButtonExists(); + await ml.jobWizardCommon.createJobAndWaitForCompletion(); + }); + + it('job creation displays the created job in the job list', async () => { + await ml.navigation.navigateToMl(); + await ml.navigation.navigateToJobManagement(); + + await ml.jobTable.waitForJobsToLoad(); + await ml.jobTable.filterWithSearchString(jobId); + const rows = await ml.jobTable.parseJobTable(); + expect(rows.filter(row => row.id === jobId)).to.have.length(1); + }); + + it('job creation displays details for the created job in the job list', async () => { + await ml.jobTable.assertJobRowFields(jobId, getExpectedRow(jobId, jobGroups)); + + await ml.jobTable.assertJobRowDetailsCounts( + jobId, + getExpectedCounts(jobId), + getExpectedModelSizeStats(jobId) + ); + }); + + it('job cloning clicks the clone action and loads the single metric wizard', async () => { + await ml.jobTable.clickCloneJobAction(jobId); + await ml.jobTypeSelection.assertSingleMetricJobWizardOpen(); + }); + + it('job cloning displays the time range step', async () => { + await ml.jobWizardCommon.assertTimeRangeSectionExists(); + }); + + it('job cloning sets the timerange', async () => { + await ml.jobWizardCommon.clickUseFullDataButton( + 'Feb 7, 2016 @ 00:00:00.000', + 'Feb 11, 2016 @ 23:59:54.000' + ); + }); + + it('job cloning displays the event rate chart', async () => { + await ml.jobWizardCommon.assertEventRateChartExists(); + await ml.jobWizardCommon.assertEventRateChartHasData(); + }); + + it('job cloning displays the pick fields step', async () => { + await ml.jobWizardCommon.advanceToPickFieldsSection(); + }); + + it('job cloning pre-fills field and aggregation', async () => { + await ml.jobWizardCommon.assertAggAndFieldInputExists(); + await ml.jobWizardCommon.assertAggAndFieldSelection([aggAndFieldIdentifier]); + await ml.jobWizardCommon.assertAnomalyChartExists('LINE'); + }); + + it('job cloning pre-fills the bucket span', async () => { + await ml.jobWizardCommon.assertBucketSpanInputExists(); + await ml.jobWizardCommon.assertBucketSpanValue(bucketSpan); + }); + + it('job cloning displays the job details step', async () => { + await ml.jobWizardCommon.advanceToJobDetailsSection(); + }); + + it('job cloning does not pre-fill the job id', async () => { + await ml.jobWizardCommon.assertJobIdInputExists(); + await ml.jobWizardCommon.assertJobIdValue(''); + }); + + it('job cloning inputs the clone job id', async () => { + await ml.jobWizardCommon.setJobId(jobIdClone); + }); + + it('job cloning pre-fills the job description', async () => { + await ml.jobWizardCommon.assertJobDescriptionInputExists(); + await ml.jobWizardCommon.assertJobDescriptionValue(jobDescription); + }); + + it('job cloning pre-fills job groups', async () => { + await ml.jobWizardCommon.assertJobGroupInputExists(); + await ml.jobWizardCommon.assertJobGroupSelection(jobGroups); + }); + + it('job cloning inputs the clone job group', async () => { + await ml.jobWizardCommon.assertJobGroupInputExists(); + await ml.jobWizardCommon.addJobGroup('clone'); + await ml.jobWizardCommon.assertJobGroupSelection(jobGroupsClone); + }); + + it('job cloning opens the advanced section', async () => { + await ml.jobWizardCommon.ensureAdvancedSectionOpen(); + }); + + it('job cloning pre-fills the model plot switch', async () => { + await ml.jobWizardCommon.assertModelPlotSwitchExists(); + await ml.jobWizardCommon.assertModelPlotSwitchCheckedState(true); + }); + + it('job cloning pre-fills the dedicated index switch', async () => { + await ml.jobWizardCommon.assertDedicatedIndexSwitchExists(); + await ml.jobWizardCommon.assertDedicatedIndexSwitchCheckedState(true); + }); + + it('job cloning pre-fills the model memory limit', async () => { + await ml.jobWizardCommon.assertModelMemoryLimitInputExists(); + await ml.jobWizardCommon.assertModelMemoryLimitValue(memoryLimit); + }); + + it('job cloning displays the validation step', async () => { + await ml.jobWizardCommon.advanceToValidationSection(); + }); + + it('job cloning displays the summary step', async () => { + await ml.jobWizardCommon.advanceToSummarySection(); + }); + + it('job cloning creates the job and finishes processing', async () => { + await ml.jobWizardCommon.assertCreateJobButtonExists(); + await ml.jobWizardCommon.createJobAndWaitForCompletion(); + }); + + it('job cloning displays the created job in the job list', async () => { + await ml.navigation.navigateToMl(); + await ml.navigation.navigateToJobManagement(); + + await ml.jobTable.waitForJobsToLoad(); + await ml.jobTable.filterWithSearchString(jobIdClone); + const rows = await ml.jobTable.parseJobTable(); + expect(rows.filter(row => row.id === jobIdClone)).to.have.length(1); + }); + + it('job cloning displays details for the created job in the job list', async () => { + await ml.jobTable.assertJobRowFields(jobIdClone, getExpectedRow(jobIdClone, jobGroupsClone)); + + await ml.jobTable.assertJobRowDetailsCounts( + jobIdClone, + getExpectedCounts(jobIdClone), + getExpectedModelSizeStats(jobIdClone) + ); + }); + + it('job deletion has results for the job before deletion', async () => { + await ml.api.assertJobResultsExist(jobIdClone); + }); + + it('job deletion triggers the delete action', async () => { + await ml.jobTable.clickDeleteJobAction(jobIdClone); + }); + + it('job deletion confirms the delete modal', async () => { + await ml.jobTable.confirmDeleteJobModal(); + }); + + it('job deletion does not display the deleted job in the job list any more', async () => { + await ml.jobTable.waitForJobsToLoad(); + await ml.jobTable.filterWithSearchString(jobIdClone); + const rows = await ml.jobTable.parseJobTable(); + expect(rows.filter(row => row.id === jobIdClone)).to.have.length(0); + }); + + it('job deletion does not have results for the deleted job any more', async () => { + await ml.api.assertNoJobResultsExist(jobIdClone); }); }); } diff --git a/x-pack/test/functional/apps/monitoring/time_filter.js b/x-pack/test/functional/apps/monitoring/time_filter.js index e4254b487adfd..0afcada14be5f 100644 --- a/x-pack/test/functional/apps/monitoring/time_filter.js +++ b/x-pack/test/functional/apps/monitoring/time_filter.js @@ -27,7 +27,8 @@ export default function ({ getService, getPageObjects }) { await tearDown(); }); - it('should send another request when clicking Refresh', async () => { + // FLAKY: https://github.com/elastic/kibana/issues/48910 + it.skip('should send another request when clicking Refresh', async () => { await testSubjects.click('querySubmitButton'); const isLoading = await PageObjects.header.isGlobalLoadingIndicatorVisible(); expect(isLoading).to.be(true); diff --git a/x-pack/test/functional/config.ie.js b/x-pack/test/functional/config.ie.js index 7a46471726fcf..af4e9f2e4866f 100644 --- a/x-pack/test/functional/config.ie.js +++ b/x-pack/test/functional/config.ie.js @@ -34,7 +34,6 @@ export default async function ({ readConfigFile }) { // require.resolve(__dirname, './apps/status_page'), // require.resolve(__dirname, './apps/timelion'), // require.resolve(__dirname, './apps/upgrade_assistant'), - // require.resolve(__dirname, './apps/code'), // require.resolve(__dirname, './apps/visualize'), // require.resolve(__dirname, './apps/uptime'), // require.resolve(__dirname, './apps/saved_objects_management'), diff --git a/x-pack/test/functional/config.js b/x-pack/test/functional/config.js index 3ec86a7fc1722..f8cedc03865bb 100644 --- a/x-pack/test/functional/config.js +++ b/x-pack/test/functional/config.js @@ -44,7 +44,6 @@ export default async function ({ readConfigFile }) { resolve(__dirname, './apps/status_page'), resolve(__dirname, './apps/timelion'), resolve(__dirname, './apps/upgrade_assistant'), - resolve(__dirname, './apps/code'), resolve(__dirname, './apps/visualize'), resolve(__dirname, './apps/uptime'), resolve(__dirname, './apps/saved_objects_management'), diff --git a/x-pack/test/functional/es_archives/code/repositories/code_examples_flatten_directory/data.json.gz b/x-pack/test/functional/es_archives/code/repositories/code_examples_flatten_directory/data.json.gz deleted file mode 100644 index 8c9134e2ce38b..0000000000000 Binary files a/x-pack/test/functional/es_archives/code/repositories/code_examples_flatten_directory/data.json.gz and /dev/null differ diff --git a/x-pack/test/functional/es_archives/code/repositories/code_examples_flatten_directory/mappings.json b/x-pack/test/functional/es_archives/code/repositories/code_examples_flatten_directory/mappings.json deleted file mode 100644 index f30d59647b188..0000000000000 --- a/x-pack/test/functional/es_archives/code/repositories/code_examples_flatten_directory/mappings.json +++ /dev/null @@ -1,480 +0,0 @@ -{ - "type": "index", - "value": { - "aliases": { - ".code-document-github.com-elastic-code-examples_flatten-directory-40bb5fb7": { - } - }, - "index": ".code-document-github.com-elastic-code-examples_flatten-directory-40bb5fb7-1", - "mappings": { - "_meta": { - "version": 1 - }, - "dynamic_templates": [ - { - "fieldDefaultNotAnalyzed": { - "mapping": { - "index": false, - "norms": false - }, - "match": "*" - } - } - ], - "properties": { - "content": { - "analyzer": "content_analyzer", - "type": "text" - }, - "language": { - "type": "keyword" - }, - "path": { - "analyzer": "path_analyzer", - "fields": { - "hierarchy": { - "analyzer": "path_hierarchy_analyzer", - "type": "text" - }, - "keyword": { - "type": "keyword" - } - }, - "type": "text" - }, - "qnames": { - "analyzer": "qname_path_hierarchy_analyzer", - "type": "text" - }, - "repoUri": { - "type": "keyword" - }, - "repository": { - "properties": { - "defaultBranch": { - "type": "keyword" - }, - "indexedRevision": { - "type": "keyword" - }, - "name": { - "type": "text" - }, - "nextUpdateTimestamp": { - "index": false, - "type": "date" - }, - "org": { - "type": "text" - }, - "protocol": { - "index": false, - "norms": false, - "type": "text" - }, - "revision": { - "type": "keyword" - }, - "uri": { - "type": "text" - }, - "url": { - "index": false, - "type": "text" - } - } - }, - "repository_config": { - "properties": { - "disableGo": { - "type": "boolean" - }, - "disableJava": { - "type": "boolean" - }, - "disableTypescript": { - "type": "boolean" - }, - "uri": { - "type": "text" - } - } - }, - "repository_delete_status": { - "properties": { - "progress": { - "type": "integer" - }, - "revision": { - "type": "keyword" - }, - "timestamp": { - "type": "date" - }, - "uri": { - "type": "text" - } - } - }, - "repository_git_status": { - "properties": { - "cloneProgress": { - "properties": { - "indexedDeltas": { - "type": "integer" - }, - "indexedObjects": { - "type": "integer" - }, - "isCloned": { - "type": "boolean" - }, - "localObjects": { - "type": "integer" - }, - "receivedBytes": { - "type": "integer" - }, - "receivedObjects": { - "type": "integer" - }, - "totalDeltas": { - "type": "integer" - }, - "totalObjects": { - "type": "integer" - } - } - }, - "errorMessage": { - "type": "text" - }, - "progress": { - "type": "integer" - }, - "revision": { - "type": "keyword" - }, - "timestamp": { - "type": "date" - }, - "uri": { - "type": "text" - } - } - }, - "repository_index_status": { - "properties": { - "commitIndexProgress": { - "properties": { - "checkpoint": { - "type": "object" - }, - "fail": { - "type": "integer" - }, - "percentage": { - "type": "integer" - }, - "success": { - "type": "integer" - }, - "total": { - "type": "integer" - }, - "type": { - "type": "keyword" - } - } - }, - "indexProgress": { - "properties": { - "checkpoint": { - "properties": { - "filePath": { - "index": false, - "norms": false, - "type": "text" - }, - "repoUri": { - "index": false, - "norms": false, - "type": "text" - }, - "revision": { - "index": false, - "norms": false, - "type": "text" - } - } - }, - "fail": { - "type": "integer" - }, - "percentage": { - "type": "integer" - }, - "success": { - "type": "integer" - }, - "total": { - "type": "integer" - }, - "type": { - "type": "keyword" - } - } - }, - "progress": { - "type": "integer" - }, - "revision": { - "type": "keyword" - }, - "timestamp": { - "type": "date" - }, - "uri": { - "type": "text" - } - } - }, - "repository_random_path": { - "type": "keyword" - }, - "sha1": { - "index": false, - "norms": false, - "type": "text" - } - } - }, - "settings": { - "index": { - "analysis": { - "analyzer": { - "content_analyzer": { - "char_filter": [ - "content_char_filter" - ], - "filter": [ - "lowercase" - ], - "tokenizer": "standard" - }, - "lowercase_analyzer": { - "filter": [ - "lowercase" - ], - "tokenizer": "keyword", - "type": "custom" - }, - "path_analyzer": { - "filter": [ - "lowercase" - ], - "tokenizer": "path_tokenizer", - "type": "custom" - }, - "path_hierarchy_analyzer": { - "filter": [ - "lowercase" - ], - "tokenizer": "path_hierarchy_tokenizer", - "type": "custom" - }, - "qname_path_hierarchy_analyzer": { - "filter": [ - "lowercase" - ], - "tokenizer": "qname_path_hierarchy_tokenizer", - "type": "custom" - } - }, - "char_filter": { - "content_char_filter": { - "pattern": "[.]", - "replacement": " ", - "type": "pattern_replace" - } - }, - "tokenizer": { - "path_hierarchy_tokenizer": { - "delimiter": "/", - "reverse": "true", - "type": "path_hierarchy" - }, - "path_tokenizer": { - "pattern": "[\\\\./]", - "type": "pattern" - }, - "qname_path_hierarchy_tokenizer": { - "delimiter": ".", - "reverse": "true", - "type": "path_hierarchy" - } - } - }, - "auto_expand_replicas": "0-1", - "number_of_replicas": "0", - "number_of_shards": "1" - } - } - } -} - -{ - "type": "index", - "value": { - "aliases": { - ".code-reference-github.com-elastic-code-examples_flatten-directory-40bb5fb7": { - } - }, - "index": ".code-reference-github.com-elastic-code-examples_flatten-directory-40bb5fb7-1", - "mappings": { - "_meta": { - "version": 1 - }, - "dynamic_templates": [ - { - "fieldDefaultNotAnalyzed": { - "mapping": { - "index": false, - "norms": false - }, - "match": "*" - } - } - ], - "properties": { - "category": { - "type": "keyword" - }, - "location": { - "properties": { - "uri": { - "type": "text" - } - } - }, - "symbol": { - "properties": { - "kind": { - "type": "keyword" - }, - "location": { - "properties": { - "uri": { - "type": "text" - } - } - }, - "name": { - "type": "text" - } - } - } - } - }, - "settings": { - "index": { - "auto_expand_replicas": "0-1", - "number_of_replicas": "0", - "number_of_shards": "1" - } - } - } -} - -{ - "type": "index", - "value": { - "aliases": { - ".code-symbol-github.com-elastic-code-examples_flatten-directory-40bb5fb7": { - } - }, - "index": ".code-symbol-github.com-elastic-code-examples_flatten-directory-40bb5fb7-1", - "mappings": { - "_meta": { - "version": 1 - }, - "dynamic_templates": [ - { - "fieldDefaultNotAnalyzed": { - "mapping": { - "index": false, - "norms": false - }, - "match": "*" - } - } - ], - "properties": { - "qname": { - "analyzer": "qname_path_hierarchy_case_sensitive_analyzer", - "fields": { - "lowercased": { - "analyzer": "qname_path_hierarchy_case_insensitive_analyzer", - "type": "text" - } - }, - "type": "text" - }, - "symbolInformation": { - "properties": { - "kind": { - "index": false, - "type": "integer" - }, - "location": { - "properties": { - "uri": { - "type": "keyword" - } - } - }, - "name": { - "analyzer": "qname_path_hierarchy_case_sensitive_analyzer", - "fields": { - "lowercased": { - "analyzer": "qname_path_hierarchy_case_insensitive_analyzer", - "type": "text" - } - }, - "type": "text" - } - } - } - } - }, - "settings": { - "index": { - "analysis": { - "analyzer": { - "qname_path_hierarchy_case_insensitive_analyzer": { - "filter": [ - "lowercase" - ], - "tokenizer": "qname_path_hierarchy_tokenizer", - "type": "custom" - }, - "qname_path_hierarchy_case_sensitive_analyzer": { - "tokenizer": "qname_path_hierarchy_tokenizer", - "type": "custom" - } - }, - "tokenizer": { - "qname_path_hierarchy_tokenizer": { - "delimiter": ".", - "reverse": "true", - "type": "path_hierarchy" - } - } - }, - "auto_expand_replicas": "0-1", - "number_of_replicas": "0", - "number_of_shards": "1" - } - } - } -} diff --git a/x-pack/test/functional/es_archives/code/repositories/typescript_node_starter/data.json.gz b/x-pack/test/functional/es_archives/code/repositories/typescript_node_starter/data.json.gz deleted file mode 100644 index 5ba03fcf9b56f..0000000000000 Binary files a/x-pack/test/functional/es_archives/code/repositories/typescript_node_starter/data.json.gz and /dev/null differ diff --git a/x-pack/test/functional/es_archives/code/repositories/typescript_node_starter/mappings.json b/x-pack/test/functional/es_archives/code/repositories/typescript_node_starter/mappings.json deleted file mode 100644 index da0033cbfd8b8..0000000000000 --- a/x-pack/test/functional/es_archives/code/repositories/typescript_node_starter/mappings.json +++ /dev/null @@ -1,546 +0,0 @@ -{ - "type": "index", - "value": { - "aliases": { - ".code-document-github.com-elastic-typescript-node-starter-f7555f82": { - } - }, - "index": ".code-document-github.com-elastic-typescript-node-starter-f7555f82-1", - "mappings": { - "_meta": { - "version": 1 - }, - "dynamic_templates": [ - { - "fieldDefaultNotAnalyzed": { - "mapping": { - "index": false, - "norms": false - }, - "match": "*" - } - } - ], - "properties": { - "content": { - "analyzer": "content_analyzer", - "type": "text" - }, - "language": { - "type": "keyword" - }, - "path": { - "analyzer": "path_analyzer", - "fields": { - "hierarchy": { - "analyzer": "path_hierarchy_analyzer", - "type": "text" - }, - "keyword": { - "type": "keyword" - } - }, - "type": "text" - }, - "qnames": { - "analyzer": "qname_path_hierarchy_analyzer", - "type": "text" - }, - "repoUri": { - "type": "keyword" - }, - "repository": { - "properties": { - "defaultBranch": { - "type": "keyword" - }, - "indexedRevision": { - "type": "keyword" - }, - "name": { - "type": "text" - }, - "nextUpdateTimestamp": { - "index": false, - "type": "date" - }, - "org": { - "type": "text" - }, - "protocol": { - "index": false, - "norms": false, - "type": "text" - }, - "revision": { - "type": "keyword" - }, - "uri": { - "type": "text" - }, - "url": { - "index": false, - "type": "text" - } - } - }, - "repository_config": { - "properties": { - "disableGo": { - "type": "boolean" - }, - "disableJava": { - "type": "boolean" - }, - "disableTypescript": { - "type": "boolean" - }, - "uri": { - "type": "text" - } - } - }, - "repository_delete_status": { - "properties": { - "progress": { - "type": "integer" - }, - "revision": { - "type": "keyword" - }, - "timestamp": { - "type": "date" - }, - "uri": { - "type": "text" - } - } - }, - "repository_git_status": { - "properties": { - "cloneProgress": { - "properties": { - "indexedDeltas": { - "type": "integer" - }, - "indexedObjects": { - "type": "integer" - }, - "isCloned": { - "type": "boolean" - }, - "localObjects": { - "type": "integer" - }, - "receivedBytes": { - "type": "integer" - }, - "receivedObjects": { - "type": "integer" - }, - "totalDeltas": { - "type": "integer" - }, - "totalObjects": { - "type": "integer" - } - } - }, - "errorMessage": { - "type": "text" - }, - "progress": { - "type": "integer" - }, - "revision": { - "type": "keyword" - }, - "timestamp": { - "type": "date" - }, - "uri": { - "type": "text" - } - } - }, - "repository_index_status": { - "properties": { - "commitIndexProgress": { - "properties": { - "checkpoint": { - "type": "object" - }, - "fail": { - "type": "integer" - }, - "percentage": { - "type": "integer" - }, - "success": { - "type": "integer" - }, - "total": { - "type": "integer" - }, - "type": { - "type": "keyword" - } - } - }, - "indexProgress": { - "properties": { - "checkpoint": { - "properties": { - "filePath": { - "index": false, - "norms": false, - "type": "text" - }, - "repoUri": { - "index": false, - "norms": false, - "type": "text" - }, - "revision": { - "index": false, - "norms": false, - "type": "text" - } - } - }, - "fail": { - "type": "integer" - }, - "percentage": { - "type": "integer" - }, - "success": { - "type": "integer" - }, - "total": { - "type": "integer" - }, - "type": { - "type": "keyword" - } - } - }, - "progress": { - "type": "integer" - }, - "revision": { - "type": "keyword" - }, - "timestamp": { - "type": "date" - }, - "uri": { - "type": "text" - } - } - }, - "repository_random_path": { - "type": "keyword" - }, - "sha1": { - "index": false, - "norms": false, - "type": "text" - } - } - }, - "settings": { - "index": { - "analysis": { - "analyzer": { - "content_analyzer": { - "char_filter": [ - "content_char_filter" - ], - "filter": [ - "lowercase" - ], - "tokenizer": "standard" - }, - "lowercase_analyzer": { - "filter": [ - "lowercase" - ], - "tokenizer": "keyword", - "type": "custom" - }, - "path_analyzer": { - "filter": [ - "lowercase" - ], - "tokenizer": "path_tokenizer", - "type": "custom" - }, - "path_hierarchy_analyzer": { - "filter": [ - "lowercase" - ], - "tokenizer": "path_hierarchy_tokenizer", - "type": "custom" - }, - "qname_path_hierarchy_analyzer": { - "filter": [ - "lowercase" - ], - "tokenizer": "qname_path_hierarchy_tokenizer", - "type": "custom" - } - }, - "char_filter": { - "content_char_filter": { - "pattern": "[.]", - "replacement": " ", - "type": "pattern_replace" - } - }, - "tokenizer": { - "path_hierarchy_tokenizer": { - "delimiter": "/", - "reverse": "true", - "type": "path_hierarchy" - }, - "path_tokenizer": { - "pattern": "[\\\\./]", - "type": "pattern" - }, - "qname_path_hierarchy_tokenizer": { - "delimiter": ".", - "reverse": "true", - "type": "path_hierarchy" - } - } - }, - "auto_expand_replicas": "0-1", - "number_of_replicas": "0", - "number_of_shards": "1" - } - } - } -} - -{ - "type": "index", - "value": { - "aliases": { - ".code-reference-github.com-elastic-typescript-node-starter-f7555f82": { - } - }, - "index": ".code-reference-github.com-elastic-typescript-node-starter-f7555f82-1", - "mappings": { - "_meta": { - "version": 1 - }, - "dynamic_templates": [ - { - "fieldDefaultNotAnalyzed": { - "mapping": { - "index": false, - "norms": false - }, - "match": "*" - } - } - ], - "properties": { - "category": { - "type": "keyword" - }, - "location": { - "properties": { - "uri": { - "type": "text" - } - } - }, - "symbol": { - "properties": { - "kind": { - "type": "keyword" - }, - "location": { - "properties": { - "uri": { - "type": "text" - } - } - }, - "name": { - "type": "text" - } - } - } - } - }, - "settings": { - "index": { - "auto_expand_replicas": "0-1", - "number_of_replicas": "0", - "number_of_shards": "1" - } - } - } -} - -{ - "type": "index", - "value": { - "aliases": { - ".code-symbol-github.com-elastic-typescript-node-starter-f7555f82": { - } - }, - "index": ".code-symbol-github.com-elastic-typescript-node-starter-f7555f82-1", - "mappings": { - "_meta": { - "version": 1 - }, - "dynamic_templates": [ - { - "fieldDefaultNotAnalyzed": { - "mapping": { - "index": false, - "norms": false - }, - "match": "*" - } - } - ], - "properties": { - "contents": { - "properties": { - "language": { - "index": false, - "norms": false, - "type": "text" - }, - "value": { - "index": false, - "norms": false, - "type": "text" - } - } - }, - "package": { - "properties": { - "name": { - "index": false, - "norms": false, - "type": "text" - }, - "repoUri": { - "index": false, - "norms": false, - "type": "text" - }, - "version": { - "index": false, - "norms": false, - "type": "text" - } - } - }, - "qname": { - "analyzer": "qname_path_hierarchy_case_sensitive_analyzer", - "fields": { - "lowercased": { - "analyzer": "qname_path_hierarchy_case_insensitive_analyzer", - "type": "text" - } - }, - "type": "text" - }, - "symbolInformation": { - "properties": { - "containerName": { - "index": false, - "norms": false, - "type": "text" - }, - "kind": { - "index": false, - "type": "integer" - }, - "location": { - "properties": { - "range": { - "properties": { - "end": { - "properties": { - "character": { - "index": false, - "type": "long" - }, - "line": { - "index": false, - "type": "long" - } - } - }, - "start": { - "properties": { - "character": { - "index": false, - "type": "long" - }, - "line": { - "index": false, - "type": "long" - } - } - } - } - }, - "uri": { - "type": "keyword" - } - } - }, - "name": { - "analyzer": "qname_path_hierarchy_case_sensitive_analyzer", - "fields": { - "lowercased": { - "analyzer": "qname_path_hierarchy_case_insensitive_analyzer", - "type": "text" - } - }, - "type": "text" - } - } - } - } - }, - "settings": { - "index": { - "analysis": { - "analyzer": { - "qname_path_hierarchy_case_insensitive_analyzer": { - "filter": [ - "lowercase" - ], - "tokenizer": "qname_path_hierarchy_tokenizer", - "type": "custom" - }, - "qname_path_hierarchy_case_sensitive_analyzer": { - "tokenizer": "qname_path_hierarchy_tokenizer", - "type": "custom" - } - }, - "tokenizer": { - "qname_path_hierarchy_tokenizer": { - "delimiter": ".", - "reverse": "true", - "type": "path_hierarchy" - } - } - }, - "auto_expand_replicas": "0-1", - "number_of_replicas": "0", - "number_of_shards": "1" - } - } - } -} \ No newline at end of file diff --git a/x-pack/test/functional/services/machine_learning/job_wizard_common.ts b/x-pack/test/functional/services/machine_learning/job_wizard_common.ts index 08f1fb92d3487..73764e8f36518 100644 --- a/x-pack/test/functional/services/machine_learning/job_wizard_common.ts +++ b/x-pack/test/functional/services/machine_learning/job_wizard_common.ts @@ -70,17 +70,16 @@ export function MachineLearningJobWizardCommonProvider({ getService }: FtrProvid await testSubjects.existOrFail('mlJobWizardAggSelection > comboBoxInput'); }, - async assertAggAndFieldSelection(expectedIdentifier: string) { + async assertAggAndFieldSelection(expectedIdentifier: string[]) { const comboBoxSelectedOptions = await comboBox.getComboBoxSelectedOptions( 'mlJobWizardAggSelection > comboBoxInput' ); - expect(comboBoxSelectedOptions.length).to.eql(1); - expect(comboBoxSelectedOptions[0]).to.eql(expectedIdentifier); + expect(comboBoxSelectedOptions).to.eql(expectedIdentifier); }, async selectAggAndField(identifier: string, isIdentifierKeptInField: boolean) { await comboBox.set('mlJobWizardAggSelection > comboBoxInput', identifier); - await this.assertAggAndFieldSelection(isIdentifierKeptInField ? identifier : ''); + await this.assertAggAndFieldSelection(isIdentifierKeptInField ? [identifier] : []); }, async assertBucketSpanInputExists() { diff --git a/yarn.lock b/yarn.lock index 606487f607648..6677663cd6f85 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1064,13 +1064,12 @@ debug "^3.1.0" lodash.once "^4.1.1" -"@elastic/charts@^13.5.4": - version "13.5.4" - resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-13.5.4.tgz#b958639d485ed58d116ac66a16e4accef97919e0" - integrity sha512-F+aE4VgJ7xvl9RmWtJiHZwADQT9FwBSWgQS1IR1VQ5wLyOI9LUwrDGZXMtLR56M7wjPWbk0228n5xPgjsuBcdg== +"@elastic/charts@^13.5.9": + version "13.5.9" + resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-13.5.9.tgz#8e27ec7de934e20a9b853921cd453a372be99ef4" + integrity sha512-H5xsW/tEpjZhm0FpZThMyjuVBWlcXF2ImpfTWYv13p8GKmorCyQWbePau9Ya8N3lMmkHUMH2e95ifd3K3g9RgA== dependencies: "@types/d3-shape" "^1.3.1" - "@types/luxon" "^1.11.1" classnames "^2.2.6" d3-array "^1.2.4" d3-collection "^1.0.7" @@ -1078,7 +1077,6 @@ d3-shape "^1.3.4" fp-ts "^1.14.2" konva "^2.6.0" - luxon "^1.11.3" mobx "^4.9.2" mobx-react "^5.4.3" newtype-ts "^0.2.4" @@ -1153,10 +1151,10 @@ tabbable "^1.1.0" uuid "^3.1.0" -"@elastic/eui@14.5.0": - version "14.5.0" - resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-14.5.0.tgz#1b27559cd644403e2692cab1549082cbd3421dab" - integrity sha512-QpmfO6Unt4evb4P18CyWzassLFrw/4CUPFYZXQK12IChDOs6GrwQ6xZM8f0wjN/NXwjRjlWzMhtCWHYmrI6NGg== +"@elastic/eui@14.7.0": + version "14.7.0" + resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-14.7.0.tgz#dcbd3e9a9307e52a2fdca5833a116de5940de06d" + integrity sha512-IjYjUqhfqjqG6cbaTANiuHyWq3U62ODzcOnIKACxHOGCK2JVwiDvtDByAuj3PvD0wG/cDN49oNbUZ/o0QCapVw== dependencies: "@types/lodash" "^4.14.116" "@types/numeral" "^0.0.25" @@ -2420,35 +2418,6 @@ resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5" integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ== -"@slack/client@^4.8.0": - version "4.8.0" - resolved "https://registry.yarnpkg.com/@slack/client/-/client-4.8.0.tgz#265606f1cebae1d72f3fdd2cdf7cf1510783dde4" - integrity sha512-c4PKsRMtTp3QVYg+6cNqqxbU/50gnYfMlZgPCGUuMDMm9mkx50y0PEuERcVyLIe5j61imrhQx9DoNIfybEhTTw== - dependencies: - "@types/form-data" "^2.2.1" - "@types/is-stream" "^1.1.0" - "@types/loglevel" "^1.5.3" - "@types/node" ">=6.0.0" - "@types/p-cancelable" "^0.3.0" - "@types/p-queue" "^2.3.1" - "@types/p-retry" "^1.0.1" - "@types/retry" "^0.10.2" - "@types/ws" "^5.1.1" - axios "^0.18.0" - eventemitter3 "^3.0.0" - finity "^0.5.4" - form-data "^2.3.1" - is-stream "^1.1.0" - loglevel "^1.6.1" - object.entries "^1.0.4" - object.getownpropertydescriptors "^2.0.3" - object.values "^1.0.4" - p-cancelable "^0.3.0" - p-queue "^2.3.0" - p-retry "^2.0.0" - retry "^0.12.0" - ws "^5.2.0" - "@slack/types@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@slack/types/-/types-1.0.0.tgz#1dc7a63b293c4911e474197585c3feda012df17a" @@ -3106,6 +3075,11 @@ resolved "https://registry.yarnpkg.com/@types/base64-js/-/base64-js-1.2.5.tgz#582b2476169a6cba460a214d476c744441d873d5" integrity sha1-WCskdhaabLpGCiFNR2x0REHYc9U= +"@types/bluebird@*": + version "3.5.28" + resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.28.tgz#04c1a520ff076649236bc8ca21198542ce2bdb09" + integrity sha512-0Vk/kqkukxPKSzP9c8WJgisgGDx5oZDbsLLWIP5t70yThO/YleE+GEm2S1GlRALTaack3O7U5OS5qEm7q2kciA== + "@types/bluebird@^3.1.1": version "3.5.20" resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.20.tgz#f6363172add6f4eabb8cada53ca9af2781e8d6a1" @@ -3295,13 +3269,6 @@ resolved "https://registry.yarnpkg.com/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86" integrity sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA== -"@types/execa@^0.9.0": - version "0.9.0" - resolved "https://registry.yarnpkg.com/@types/execa/-/execa-0.9.0.tgz#9b025d2755f17e80beaf9368c3f4f319d8b0fb93" - integrity sha512-mgfd93RhzjYBUHHV532turHC2j4l/qxsF/PbfDmprHDEUHmNZGlDn1CEsulGK3AfsPdhkWzZQT/S/k0UGhLGsA== - dependencies: - "@types/node" "*" - "@types/fancy-log@^1.3.1": version "1.3.1" resolved "https://registry.yarnpkg.com/@types/fancy-log/-/fancy-log-1.3.1.tgz#dd94fbc8c2e2ab8ab402ca8d04bb8c34965f0696" @@ -3317,13 +3284,6 @@ resolved "https://registry.yarnpkg.com/@types/file-saver/-/file-saver-2.0.0.tgz#cbb49815a5e1129d5f23836a98d65d93822409af" integrity sha512-dxdRrUov2HVTbSRFX+7xwUPlbGYVEZK6PrSqClg2QPos3PNe0bCajkDDkDeeC1znjSH03KOEqVbXpnJuWa2wgQ== -"@types/form-data@^2.2.1": - version "2.2.1" - resolved "https://registry.yarnpkg.com/@types/form-data/-/form-data-2.2.1.tgz#ee2b3b8eaa11c0938289953606b745b738c54b1e" - integrity sha512-JAMFhOaHIciYVh8fb5/83nmuO/AHwmto+Hq7a9y8FzLDcC1KCU344XDOMEmahnrTFlHjgh4L0WJFczNIX2GxnQ== - dependencies: - "@types/node" "*" - "@types/fs-extra@5.0.4": version "5.0.4" resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-5.0.4.tgz#b971134d162cc0497d221adde3dbb67502225599" @@ -3460,11 +3420,6 @@ resolved "https://registry.yarnpkg.com/@types/hoek/-/hoek-4.1.3.tgz#d1982d48fb0d2a0e5d7e9d91838264d8e428d337" integrity sha1-0ZgtSPsNKg5dfp2Rg4Jk2OQo0zc= -"@types/humps@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@types/humps/-/humps-1.1.2.tgz#fbcaf596d20ff2ed78f8f511c5d6a943b51101d6" - integrity sha1-+8r1ltIP8u14+PURxdapQ7URAdY= - "@types/indent-string@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@types/indent-string/-/indent-string-3.0.0.tgz#9ebb391ceda548926f5819ad16405349641b999f" @@ -3487,13 +3442,6 @@ resolved "https://registry.yarnpkg.com/@types/is-glob/-/is-glob-4.0.0.tgz#fb8a2bff539025d4dcd6d5efe7689e03341b876d" integrity sha512-zC/2EmD8scdsGIeE+Xg7kP7oi9VP90zgMQtm9Cr25av4V+a+k8slQyiT60qSw8KORYrOKlPXfHwoa1bQbRzskQ== -"@types/is-stream@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@types/is-stream/-/is-stream-1.1.0.tgz#b84d7bb207a210f2af9bed431dc0fbe9c4143be1" - integrity sha512-jkZatu4QVbR60mpIzjINmtS1ZF4a/FqdTUTBeQDVOQ2PYyidtwFKr0B5G6ERukKwliq+7mIXvxyppwzG5EgRYg== - dependencies: - "@types/node" "*" - "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff" @@ -3631,21 +3579,11 @@ resolved "https://registry.yarnpkg.com/@types/log-symbols/-/log-symbols-2.0.0.tgz#7919e2ec3c8d13879bfdcab310dd7a3f7fc9466d" integrity sha512-YJhbp0sz3egFFKl3BcCNPQKzuGFOP4PACcsifhK6ROGnJUW9ViYLuLybQ9GQZm7Zejy3tkGuiXYMq3GiyGkU4g== -"@types/loglevel@^1.5.3": - version "1.5.3" - resolved "https://registry.yarnpkg.com/@types/loglevel/-/loglevel-1.5.3.tgz#adfce55383edc5998a2170ad581b3e23d6adb5b8" - integrity sha512-TzzIZihV+y9kxSg5xJMkyIkaoGkXi50isZTtGHObNHRqAAwjGNjSCNPI7AUAv0tZUKTq9f2cdkCUd/2JVZUTrA== - "@types/lru-cache@^5.1.0": version "5.1.0" resolved "https://registry.yarnpkg.com/@types/lru-cache/-/lru-cache-5.1.0.tgz#57f228f2b80c046b4a1bd5cac031f81f207f4f03" integrity sha512-RaE0B+14ToE4l6UqdarKPnXwVDuigfFv+5j9Dze/Nqr23yyuqdNvzcZi3xB+3Agvi5R4EOgAksfv3lXX4vBt9w== -"@types/luxon@^1.11.1": - version "1.12.0" - resolved "https://registry.yarnpkg.com/@types/luxon/-/luxon-1.12.0.tgz#acf14294d18e6eba427a5e5d7dfce0f5cd2a9400" - integrity sha512-+UzPmwHSEEyv7aGlNkVpuFxp/BirXgl8NnPGCtmyx2KXIzAapoW3IqSVk87/Z3PUk8vEL8Pe1HXEMJbNBOQgtg== - "@types/mapbox-gl@^0.54.1": version "0.54.3" resolved "https://registry.yarnpkg.com/@types/mapbox-gl/-/mapbox-gl-0.54.3.tgz#6215fbf4dbb555d2ca6ce3be0b1de045eec0f967" @@ -3697,10 +3635,10 @@ resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.7.tgz#315d570ccb56c53452ff8638738df60726d5b6ea" integrity sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ== -"@types/moment-timezone@^0.5.8": - version "0.5.8" - resolved "https://registry.yarnpkg.com/@types/moment-timezone/-/moment-timezone-0.5.8.tgz#92aba9bc238cabf69a27a1a4f52e0ebb8f10f896" - integrity sha512-FpC+fLd/Hmxxcl4cxeb5HTyCmEvl3b4TeX8w9J+0frdzH+UCEkexKe4WZ3DTALwLj2/hyujn8tp3zl1YdgLrxQ== +"@types/moment-timezone@^0.5.12": + version "0.5.12" + resolved "https://registry.yarnpkg.com/@types/moment-timezone/-/moment-timezone-0.5.12.tgz#0fb680c03db194fe8ff4551eaeb1eec8d3d80e9f" + integrity sha512-hnHH2+Efg2vExr/dSz+IX860nSiyk9Sk4pJF2EmS11lRpMcNXeB4KBW5xcgw2QPsb9amTXdsVNEe5IoJXiT0uw== dependencies: moment ">=2.14.0" @@ -3737,7 +3675,7 @@ dependencies: "@types/node" "*" -"@types/node@*", "@types/node@10.12.27", "@types/node@8.5.8", "@types/node@>=6.0.0", "@types/node@>=8.9.0", "@types/node@^10.12.27", "@types/node@^12.0.2": +"@types/node@*", "@types/node@10.12.27", "@types/node@8.5.8", "@types/node@>=8.9.0", "@types/node@^10.12.27", "@types/node@^12.0.2": version "10.12.27" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.27.tgz#eb3843f15d0ba0986cc7e4d734d2ee8b50709ef8" integrity sha512-e9wgeY6gaY21on3ve0xAjgBVjGDWq/xUteK0ujsE53bUoxycMkqfnkUgMt6ffZtykZ5X12Mg3T7Pw4TRCObDKg== @@ -3780,23 +3718,6 @@ dependencies: "@types/node" "*" -"@types/p-cancelable@^0.3.0": - version "0.3.0" - resolved "https://registry.yarnpkg.com/@types/p-cancelable/-/p-cancelable-0.3.0.tgz#3e4fcc54a3dfd81d0f5b93546bb68d0df50553bb" - integrity sha512-sP+9Ivnpil7cdmvr5O+145aXm65YX8Y+Lrul1ojdYz6yaE05Dqonn6Z9v5eqJCQ0UeSGcTRtepMlZDh9ywdKgw== - -"@types/p-queue@^2.3.1": - version "2.3.1" - resolved "https://registry.yarnpkg.com/@types/p-queue/-/p-queue-2.3.1.tgz#2fb251e46e884e31c4bd1bf58f0e188972353ff4" - integrity sha512-JyO7uMAtkcMMULmsTQ4t/lCC8nxirTtweGG1xAFNNIAoC1RemmeIxq8PiKghuEy99XdbS6Lwx4zpbXUjfeSSAA== - -"@types/p-retry@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@types/p-retry/-/p-retry-1.0.1.tgz#2302bc3da425014208c8a9b68293d37325124785" - integrity sha512-HgQPG9kkUb4EpTeUv2taH2nBZsVUb5aOTSw3X2YozcTG1ttmGcLaLKx1MbAz1evVfUEDTCAPmdz2HiFztIyWrw== - dependencies: - "@types/retry" "*" - "@types/papaparse@^4.5.11": version "4.5.11" resolved "https://registry.yarnpkg.com/@types/papaparse/-/papaparse-4.5.11.tgz#dcd4f64da55f768c2e2cf92ccac1973c67a73890" @@ -3996,19 +3917,6 @@ "@types/tough-cookie" "*" form-data "^2.5.0" -"@types/retry@*", "@types/retry@^0.10.2": - version "0.10.2" - resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.10.2.tgz#bd1740c4ad51966609b058803ee6874577848b37" - integrity sha512-LqJkY4VQ7S09XhI7kA3ON71AxauROhSv74639VsNXC9ish4IWHnIi98if+nP1MxQV3RMPqXSCYgpPsDHjlg9UQ== - -"@types/rimraf@^2.0.2": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@types/rimraf/-/rimraf-2.0.2.tgz#7f0fc3cf0ff0ad2a99bb723ae1764f30acaf8b6e" - integrity sha512-Hm/bnWq0TCy7jmjeN5bKYij9vw5GrDFWME4IuxV08278NtU/VdGbzsBohcCUJ7+QMqmUq5hpRKB39HeQWJjztQ== - dependencies: - "@types/glob" "*" - "@types/node" "*" - "@types/selenium-webdriver@^4.0.3": version "4.0.3" resolved "https://registry.yarnpkg.com/@types/selenium-webdriver/-/selenium-webdriver-4.0.3.tgz#388f12c464cc1fff5d4c84cb372f19b9ab9b5c81" @@ -4113,6 +4021,22 @@ "@types/cookiejar" "*" "@types/node" "*" +"@types/supertest-as-promised@^2.0.38": + version "2.0.38" + resolved "https://registry.yarnpkg.com/@types/supertest-as-promised/-/supertest-as-promised-2.0.38.tgz#5077adf2a31429e06ba8de6799ebdb796ad50fc7" + integrity sha512-2vdlnsZBIgaX0DFNOACK4xFDqvoA1sAR78QD3LiDWGmzSfrRCNt1WFyUYe2Vf0QS03tZf6XC8bNlLaLYXhZbGA== + dependencies: + "@types/bluebird" "*" + "@types/superagent" "*" + "@types/supertest" "*" + +"@types/supertest@*": + version "2.0.8" + resolved "https://registry.yarnpkg.com/@types/supertest/-/supertest-2.0.8.tgz#23801236e2b85204ed771a8e7c40febba7da2bda" + integrity sha512-wcax7/ip4XSSJRLbNzEIUVy2xjcBIZZAuSd2vtltQfRK7kxhx5WMHbLHkYdxN3wuQCrwpYrg86/9byDjPXoGMA== + dependencies: + "@types/superagent" "*" + "@types/supertest@^2.0.5": version "2.0.5" resolved "https://registry.yarnpkg.com/@types/supertest/-/supertest-2.0.5.tgz#18d082a667eaed22759be98f4923e0061ae70c62" @@ -4229,14 +4153,6 @@ resolved "https://registry.yarnpkg.com/@types/write-pkg/-/write-pkg-3.1.0.tgz#f58767f4fb9a6a3ad8e95d3e9cd1f2d026ceab26" integrity sha512-JRGsPEPCrYqTXU0Cr+Yu7esPBE2yvH7ucOHr+JuBy0F59kglPvO5gkmtyEvf3P6dASSkScvy/XQ6SC1QEBFDuA== -"@types/ws@^5.1.1": - version "5.1.2" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-5.1.2.tgz#f02d3b1cd46db7686734f3ce83bdf46c49decd64" - integrity sha512-NkTXUKTYdXdnPE2aUUbGOXE1XfMK527SCvU/9bj86kyFF6kZ9ZnOQ3mK5jADn98Y2vEUD/7wKDgZa7Qst2wYOg== - dependencies: - "@types/events" "*" - "@types/node" "*" - "@types/xml-crypto@^1.4.0": version "1.4.0" resolved "https://registry.yarnpkg.com/@types/xml-crypto/-/xml-crypto-1.4.0.tgz#b586e4819f6bdd0571a3faa9a8098049d5c3cc5a" @@ -5104,16 +5020,16 @@ ansi@^0.3.0, ansi@~0.3.1: resolved "https://registry.yarnpkg.com/ansi/-/ansi-0.3.1.tgz#0c42d4fb17160d5a9af1e484bace1c66922c1b21" integrity sha1-DELU+xcWDVqa8eSEus4cZpIsGyE= -ansicolors@0.3.2, ansicolors@~0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.3.2.tgz#665597de86a9ffe3aa9bfbe6cae5c6ea426b4979" - integrity sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk= - ansicolors@~0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.2.1.tgz#be089599097b74a5c9c4a84a0cdbcdb62bd87aef" integrity sha1-vgiVmQl7dKXJxKhKDNvNtivYeu8= +ansicolors@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.3.2.tgz#665597de86a9ffe3aa9bfbe6cae5c6ea426b4979" + integrity sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk= + any-base@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/any-base/-/any-base-1.1.0.tgz#ae101a62bc08a597b4c9ab5b7089d456630549fe" @@ -5692,16 +5608,6 @@ assign-symbols@^1.0.0: resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= -assignment@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/assignment/-/assignment-2.0.0.tgz#ffd17b21bf5d6b22e777b989681a815456a3dd3e" - integrity sha1-/9F7Ib9dayLnd7mJaBqBVFaj3T4= - -assignment@2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/assignment/-/assignment-2.2.0.tgz#f5b5bc2d160d69986e8700cd38f567c0aabe101e" - integrity sha1-9bW8LRYNaZhuhwDNOPVnwKq+EB4= - ast-module-types@^2.3.1, ast-module-types@^2.3.2, ast-module-types@^2.4.0: version "2.5.0" resolved "https://registry.yarnpkg.com/ast-module-types/-/ast-module-types-2.5.0.tgz#44b8bcd51684329a77f2af6b2587df9ea6b4d5ff" @@ -5785,55 +5691,6 @@ async-settle@^1.0.0: dependencies: async-done "^1.2.2" -async.queue@^0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/async.queue/-/async.queue-0.5.2.tgz#8d5d90812e1481066bc0904e8cc1712b17c3bd7c" - integrity sha1-jV2QgS4UgQZrwJBOjMFxKxfDvXw= - dependencies: - async.util.queue "0.5.2" - -async.util.arrayeach@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/async.util.arrayeach/-/async.util.arrayeach-0.5.2.tgz#58c4e98028d55d69bfb05aeb3af44e0a555a829c" - integrity sha1-WMTpgCjVXWm/sFrrOvROClVagpw= - -async.util.isarray@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/async.util.isarray/-/async.util.isarray-0.5.2.tgz#e62dac8f2636f65875dcf7521c2d24d0dfb2bbdf" - integrity sha1-5i2sjyY29lh13PdSHC0k0N+yu98= - -async.util.map@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/async.util.map/-/async.util.map-0.5.2.tgz#e588ef86e0b3ab5f027d97af4d6835d055ca69d6" - integrity sha1-5YjvhuCzq18CfZevTWg10FXKadY= - -async.util.noop@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/async.util.noop/-/async.util.noop-0.5.2.tgz#bdd62b97cb0aa3f60b586ad148468698975e58b9" - integrity sha1-vdYrl8sKo/YLWGrRSEaGmJdeWLk= - -async.util.onlyonce@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/async.util.onlyonce/-/async.util.onlyonce-0.5.2.tgz#b8e6fc004adc923164d79e32f2813ee465c24ff2" - integrity sha1-uOb8AErckjFk154y8oE+5GXCT/I= - -async.util.queue@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/async.util.queue/-/async.util.queue-0.5.2.tgz#57f65abe1a3cdf273d31abd28ab95425f8222ee5" - integrity sha1-V/Zavho83yc9MavSirlUJfgiLuU= - dependencies: - async.util.arrayeach "0.5.2" - async.util.isarray "0.5.2" - async.util.map "0.5.2" - async.util.noop "0.5.2" - async.util.onlyonce "0.5.2" - async.util.setimmediate "0.5.2" - -async.util.setimmediate@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/async.util.setimmediate/-/async.util.setimmediate-0.5.2.tgz#2812ebabf2a58027758d4bc7793d1ccfaf10255f" - integrity sha1-KBLrq/KlgCd1jUvHeT0cz68QJV8= - async@1.x, async@^1.4.2, async@^1.5.2, async@~1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" @@ -6134,17 +5991,6 @@ babel-plugin-emotion@^9.2.11: source-map "^0.5.7" touch "^2.0.1" -babel-plugin-inline-react-svg@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/babel-plugin-inline-react-svg/-/babel-plugin-inline-react-svg-1.1.0.tgz#b39519c78249b3fcf895b541c38b485a2b11b0be" - integrity sha512-Y/tBMi7Jh7Jh+DGcSNsY9/RW33nvcR067HFK0Dp+03jpidil1sJAffBdajK72xn3tbwMsgFLJubxW5xpQLJytA== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/parser" "^7.0.0" - lodash.isplainobject "^4.0.6" - resolve "^1.10.0" - svgo "^0.7.2" - babel-plugin-istanbul@^5.1.0: version "5.1.1" resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-5.1.1.tgz#7981590f1956d75d67630ba46f0c22493588c893" @@ -6262,11 +6108,6 @@ babel-plugin-minify-type-constructors@^0.4.3: dependencies: babel-helper-is-void-0 "^0.4.3" -babel-plugin-mock-imports@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/babel-plugin-mock-imports/-/babel-plugin-mock-imports-1.0.1.tgz#1476ed4de911347d344fc81caab4beced80804b1" - integrity sha512-Nu4unCGKeqOfLlfnLPnv/pEHancdAGTqFqyArZ27gsKIiKxeZvMr87IHB8BxhMu3Bfc8fA8bx7hWt32aZbEwpQ== - babel-plugin-named-asset-import@^0.3.1: version "0.3.3" resolved "https://registry.yarnpkg.com/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.3.tgz#9ba2f3ac4dc78b042651654f07e847adfe50667c" @@ -6326,7 +6167,7 @@ babel-plugin-transform-property-literals@^6.9.4: dependencies: esutils "^2.0.2" -babel-plugin-transform-react-remove-prop-types@0.4.24, babel-plugin-transform-react-remove-prop-types@^0.4.24: +babel-plugin-transform-react-remove-prop-types@0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz#f2edaf9b4c6a5fbe5c1d678bfb531078c1555f3a" integrity sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA== @@ -7769,13 +7610,6 @@ check-more-types@2.24.0: resolved "https://registry.yarnpkg.com/check-more-types/-/check-more-types-2.24.0.tgz#1420ffb10fd444dcfc79b43891bbfffd32a84600" integrity sha1-FCD/sQ/URNz8ebQ4kbv//TKoRgA= -checksum@0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/checksum/-/checksum-0.1.1.tgz#dc6527d4c90be8560dbd1ed4cecf3297d528e9e9" - integrity sha1-3GUn1MkL6FYNvR7Uzs8yl9Uo6ek= - dependencies: - optimist "~0.3.5" - cheerio@0.22.0: version "0.22.0" resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-0.22.0.tgz#a9baa860a3f9b595a6b81b1a86873121ed3a269e" @@ -7929,13 +7763,6 @@ circular-json@^0.5.5: resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.5.9.tgz#932763ae88f4f7dead7a0d09c8a51a4743a53b1d" integrity sha512-4ivwqHpIFJZBuhN3g/pEcdbnGUywkBblloGbkglyloVjjR3uT6tieI89MVOfbP2tHX5sgb01FuLgAOzebNlJNQ== -clap@^1.0.9: - version "1.2.3" - resolved "https://registry.yarnpkg.com/clap/-/clap-1.2.3.tgz#4f36745b32008492557f46412d66d50cb99bce51" - integrity sha512-4CoL/A3hf90V3VIEjeuhSvlGFEHKzOz+Wfc2IVZc+FaUgU0ZQafJTP49fvnULipOPcAfqhyI2duwQyns6xqjYA== - dependencies: - chalk "^1.1.3" - class-extend@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/class-extend/-/class-extend-0.1.2.tgz#8057a82b00f53f82a5d62c50ef8cffdec6fabc34" @@ -7959,7 +7786,7 @@ classnames@2.2.6, classnames@^2.2.3, classnames@^2.2.5, classnames@^2.2.6: resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce" integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q== -classnames@2.x, classnames@^2.2.4: +classnames@2.x: version "2.2.5" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.5.tgz#fb3801d453467649ef3603c7d61a02bd129bde6d" integrity sha1-+zgB1FNGdknvNgPH1hoCvRKb3m0= @@ -8089,15 +7916,6 @@ cli-width@^2.0.0: resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk= -clipboard@^1.6.1: - version "1.7.1" - resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-1.7.1.tgz#360d6d6946e99a7a1fef395e42ba92b5e9b5a16b" - integrity sha1-Ng1taUbpmnof7zleQrqStem1oWs= - dependencies: - good-listener "^1.2.2" - select "^1.1.2" - tiny-emitter "^2.0.0" - clipboard@^2.0.0: version "2.0.4" resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.4.tgz#836dafd66cf0fea5d71ce5d5b0bf6e958009112d" @@ -8239,13 +8057,6 @@ coa@^2.0.2: chalk "^2.4.1" q "^1.1.2" -coa@~1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/coa/-/coa-1.0.4.tgz#a9ef153660d6a86a8bdec0289a5c684d217432fd" - integrity sha1-qe8VNmDWqGqL3sAomlxoTSF0Mv0= - dependencies: - q "^1.1.2" - code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" @@ -9090,6 +8901,15 @@ cross-spawn@^5.0.1, cross-spawn@^5.1.0: shebang-command "^1.2.0" which "^1.2.9" +cross-spawn@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.1.tgz#0ab56286e0f7c24e153d04cc2aa027e43a9a5d14" + integrity sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + crypt@~0.0.1: version "0.0.2" resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" @@ -9288,14 +9108,6 @@ csso@^3.5.1: dependencies: css-tree "1.0.0-alpha.29" -csso@~2.3.1: - version "2.3.2" - resolved "https://registry.yarnpkg.com/csso/-/csso-2.3.2.tgz#ddd52c587033f49e94b71fc55569f252e8ff5f85" - integrity sha1-3dUsWHAz9J6Utx/FVWnyUuj/X4U= - dependencies: - clap "^1.0.9" - source-map "^0.5.3" - cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0": version "0.3.2" resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.2.tgz#b8036170c79f07a90ff2f16e22284027a243848b" @@ -9709,11 +9521,6 @@ data-urls@^1.0.1: whatwg-mimetype "^2.1.0" whatwg-url "^7.0.0" -dataloader@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/dataloader/-/dataloader-1.4.0.tgz#bca11d867f5d3f1b9ed9f737bd15970c65dff5c8" - integrity sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw== - date-fns@^1.27.2: version "1.29.0" resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.29.0.tgz#12e609cdcb935127311d04d33334e2960a2a54e6" @@ -10008,6 +9815,14 @@ defined@~1.0.0: resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM= +del-cli@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/del-cli/-/del-cli-3.0.0.tgz#327a15d4c18d6b7e5c849a53ef0d17901bc28197" + integrity sha512-J4HDC2mpcN5aopya4VdkyiFXZaqAoo7ua9VpKbciX3DDUSbtJbPMc3ivggJsAAgS6EqonmbenIiMhBGtJPW9FA== + dependencies: + del "^5.1.0" + meow "^5.0.0" + del@^2.0.2: version "2.2.2" resolved "https://registry.yarnpkg.com/del/-/del-2.2.2.tgz#c12c981d067846c84bcaf862cff930d907ffd1a8" @@ -11840,6 +11655,22 @@ execa@^2.0.4: signal-exit "^3.0.2" strip-final-newline "^2.0.0" +execa@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-3.2.0.tgz#18326b79c7ab7fbd6610fd900c1b9e95fa48f90a" + integrity sha512-kJJfVbI/lZE1PZYDI5VPxp8zXPO9rtxOkhpZ0jMKha56AI9y2gGVC6bkukStQf0ka5Rh15BA5m7cCCH4jmHqkw== + dependencies: + cross-spawn "^7.0.0" + get-stream "^5.0.0" + human-signals "^1.1.1" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.0" + onetime "^5.1.0" + p-finally "^2.0.0" + signal-exit "^3.0.2" + strip-final-newline "^2.0.0" + execall@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/execall/-/execall-1.0.0.tgz#73d0904e395b3cab0658b08d09ec25307f29bb73" @@ -12669,11 +12500,6 @@ fined@^1.0.1: object.pick "^1.2.0" parse-filepath "^1.0.1" -finity@^0.5.4: - version "0.5.4" - resolved "https://registry.yarnpkg.com/finity/-/finity-0.5.4.tgz#f2a8a9198e8286467328ec32c8bfcc19a2229c11" - integrity sha512-3l+5/1tuw616Lgb0QBimxfdd2TqaDGpfCBpfX6EqtFmqUV3FtQnVEX4Aa62DagYEqnsTIjZcTfbq9msDbXYgyA== - first-chunk-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/first-chunk-stream/-/first-chunk-stream-2.0.0.tgz#1bdecdb8e083c0664b91945581577a43a9f31d70" @@ -14361,13 +14187,6 @@ gulp-mocha@^7.0.2: supports-color "^7.0.0" through2 "^3.0.1" -gulp-multi-process@1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/gulp-multi-process/-/gulp-multi-process-1.3.1.tgz#e12aa818e4c234357ad99d5caff8df8a18f46e9e" - integrity sha512-okxYy3mxUkekM0RNjkBg8OPuzpnD2yXMAdnGOaQPSJ2wzBdE9R9pkTV+tzPZ65ORK7b57YUc6s+gROA4+EIOLg== - dependencies: - async.queue "^0.5.2" - gulp-rename@1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/gulp-rename/-/gulp-rename-1.4.0.tgz#de1c718e7c4095ae861f7296ef4f3248648240bd" @@ -14747,11 +14566,6 @@ hawk@~6.0.2: hoek "4.x.x" sntp "2.x.x" -he@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/he/-/he-0.5.0.tgz#2c05ffaef90b68e860f3fd2b54ef580989277ee2" - integrity sha1-LAX/rvkLaOhg8/0rVO9YCYknfuI= - he@1.2.0, he@1.2.x, he@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" @@ -15125,6 +14939,11 @@ https-proxy-agent@^2.2.2: agent-base "^4.3.0" debug "^3.1.0" +human-signals@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" + integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== + humanize-ms@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" @@ -15139,11 +14958,6 @@ humanize-string@^1.0.2: dependencies: decamelize "^1.0.0" -humps@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/humps/-/humps-2.0.1.tgz#dd02ea6081bd0568dc5d073184463957ba9ef9aa" - integrity sha1-3QLqYIG9BWjcXQcxhEY5V7qe+ao= - hyperlinker@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/hyperlinker/-/hyperlinker-1.0.0.tgz#23dc9e38a206b208ee49bc2d6c8ef47027df0c0e" @@ -15622,14 +15436,6 @@ inquirer@^7.0.0: strip-ansi "^5.1.0" through "^2.3.6" -insane@2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/insane/-/insane-2.6.1.tgz#c7dcae7b51c20346883b71078fad6ce0483c198f" - integrity sha1-x9yue1HCA0aIO3EHj61s4Eg8GY8= - dependencies: - assignment "2.0.0" - he "0.5.0" - insight@0.10.1: version "0.10.1" resolved "https://registry.yarnpkg.com/insight/-/insight-0.10.1.tgz#a0ecf668484a95d66e9be59644964e719cc83380" @@ -17145,7 +16951,7 @@ js-tokens@^3.0.0, js-tokens@^3.0.2: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-yaml@3.13.1, js-yaml@3.x, js-yaml@^3.10.0, js-yaml@^3.13.1, js-yaml@^3.4.6, js-yaml@^3.5.1, js-yaml@^3.5.4, js-yaml@^3.9.0, js-yaml@~3.13.0, js-yaml@~3.13.1, js-yaml@~3.7.0: +js-yaml@3.13.1, js-yaml@3.x, js-yaml@^3.10.0, js-yaml@^3.13.1, js-yaml@^3.4.6, js-yaml@^3.5.1, js-yaml@^3.5.4, js-yaml@^3.9.0, js-yaml@~3.13.0, js-yaml@~3.13.1: version "3.13.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== @@ -18345,11 +18151,6 @@ lodash.keyby@^4.6.0: resolved "https://registry.yarnpkg.com/lodash.keyby/-/lodash.keyby-4.6.0.tgz#7f6a1abda93fd24e22728a4d361ed8bcba5a4354" integrity sha1-f2oavak/0k4icopNNh7YvLpaQ1Q= -lodash.lowercase@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/lodash.lowercase/-/lodash.lowercase-4.3.0.tgz#46515aced4acb0b7093133333af068e4c3b14e9d" - integrity sha1-RlFaztSssLcJMTMzOvBo5MOxTp0= - lodash.map@^4.4.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.map/-/lodash.map-4.6.0.tgz#771ec7839e3473d9c4cde28b19394c3562f4f6d3" @@ -18380,21 +18181,11 @@ lodash.omit@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.omit/-/lodash.omit-4.5.0.tgz#6eb19ae5a1ee1dd9df0b969e66ce0b7fa30b5e60" integrity sha1-brGa5aHuHdnfC5aeZs4Lf6MLXmA= -lodash.omitby@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.omitby/-/lodash.omitby-4.6.0.tgz#5c15ff4754ad555016b53c041311e8f079204791" - integrity sha1-XBX/R1StVVAWtTwEExHo8HkgR5E= - lodash.once@^4.0.0, lodash.once@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= -lodash.orderby@4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.orderby/-/lodash.orderby-4.6.0.tgz#e697f04ce5d78522f54d9338b32b81a3393e4eb3" - integrity sha1-5pfwTOXXhSL1TZM4syuBozk+TrM= - lodash.pad@^4.1.0: version "4.5.1" resolved "https://registry.yarnpkg.com/lodash.pad/-/lodash.pad-4.5.1.tgz#4330949a833a7c8da22cc20f6a26c4d59debba70" @@ -18420,11 +18211,6 @@ lodash.pick@^4.2.1, lodash.pick@^4.4.0: resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3" integrity sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM= -lodash.pickby@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.pickby/-/lodash.pickby-4.6.0.tgz#7dea21d8c18d7703a27c704c15d3b84a67e33aff" - integrity sha1-feoh2MGNdwOifHBMFdO4SmfjOv8= - lodash.reduce@^4.4.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.reduce/-/lodash.reduce-4.6.0.tgz#f1ab6b839299ad48f784abbf476596f03b914d3b" @@ -18581,11 +18367,6 @@ logform@^2.1.1: ms "^2.1.1" triple-beam "^1.3.0" -loglevel@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.1.tgz#e0fc95133b6ef276cdc8887cdaf24aa6f156f8fa" - integrity sha1-4PyVEztu8nbNyIh82vJKpvFW+Po= - loglevel@^1.6.4: version "1.6.4" resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.4.tgz#f408f4f006db8354d0577dcf6d33485b3cb90d56" @@ -18705,11 +18486,6 @@ lru-queue@0.1: dependencies: es5-ext "~0.10.2" -luxon@^1.11.3: - version "1.13.1" - resolved "https://registry.yarnpkg.com/luxon/-/luxon-1.13.1.tgz#b7fb7ba1e5c93ebda098af8d579314797e0a0d69" - integrity sha512-IQKRIiz9ldUrgcozN13SAeNZVYfD3bEI9X6TcrGu+dkgE4GR/Iik03ozbTM5cTr0lz8ucYPL2jtYT7Va2Flbsg== - lz-string@^1.4.4: version "1.4.4" resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26" @@ -19575,7 +19351,7 @@ mochawesome-merge@^2.0.1: uuid "^3.3.2" yargs "^12.0.5" -mochawesome-report-generator@^4.0.0, mochawesome-report-generator@^4.0.1: +mochawesome-report-generator@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/mochawesome-report-generator/-/mochawesome-report-generator-4.0.1.tgz#0a010d1ecf379eb26ba05300feb59e2665076080" integrity sha512-hQbmQt8/yCT68GjrQFat+Diqeuka3haNllexYfja1+y0hpwi3yCJwFpQCdWK9ezzcXL3Nu80f2I6SZeyspwsqg== @@ -19635,38 +19411,23 @@ module-not-found-error@^1.0.0: resolved "https://registry.yarnpkg.com/module-not-found-error/-/module-not-found-error-1.0.1.tgz#cf8b4ff4f29640674d6cdd02b0e3bc523c2bbdc0" integrity sha1-z4tP9PKWQGdNbN0CsOO8UjwrvcA= -moment-duration-format@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/moment-duration-format/-/moment-duration-format-1.3.0.tgz#541771b5f87a049cc65540475d3ad966737d6908" - integrity sha1-VBdxtfh6BJzGVUBHXTrZZnN9aQg= +moment-duration-format@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/moment-duration-format/-/moment-duration-format-2.3.2.tgz#5fa2b19b941b8d277122ff3f87a12895ec0d6212" + integrity sha512-cBMXjSW+fjOb4tyaVHuaVE/A5TqkukDWiOfxxAjY+PEqmmBQlLwn+8OzwPiG3brouXKY5Un4pBjAeB6UToXHaQ== -moment-timezone@^0.5.14: - version "0.5.14" - resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.14.tgz#4eb38ff9538b80108ba467a458f3ed4268ccfcb1" - integrity sha1-TrOP+VOLgBCLpGekWPPtQmjM/LE= +moment-timezone@^0.5.27: + version "0.5.27" + resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.27.tgz#73adec8139b6fe30452e78f210f27b1f346b8877" + integrity sha512-EIKQs7h5sAsjhPCqN6ggx6cEbs94GK050254TIJySD1bzoM5JTYDwAU1IoVOeTOL6Gm27kYJ51/uuvq1kIlrbw== dependencies: moment ">= 2.9.0" -moment@2.24.0, moment@>=1.6.0: +moment@2.24.0, "moment@>= 2.9.0", moment@>=1.6.0, moment@>=2.14.0, moment@^2.10.6, moment@^2.24.0: version "2.24.0" resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg== -"moment@>= 2.9.0", moment@^2.13.0, moment@^2.20.1: - version "2.20.1" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.20.1.tgz#d6eb1a46cbcc14a2b2f9434112c1ff8907f313fd" - integrity sha512-Yh9y73JRljxW5QxN08Fner68eFLxM5ynNOAw2LbIB1YAGeQzZT8QFSUvkAz609Zf+IHhhaUxqZK8dG3W/+HEvg== - -moment@>=2.14.0: - version "2.22.2" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66" - integrity sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y= - -moment@^2.10.6: - version "2.21.0" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.21.0.tgz#2a114b51d2a6ec9e6d83cf803f838a878d8a023a" - integrity sha512-TCZ36BjURTeFTM/CwRcViQlfkMvL1/vFISuNLO5GkcVm1+QHfbSiNqZuWeMFjj1/3+uAjXswgRk30j1kkLYJBQ== - monaco-editor@~0.17.0: version "0.17.1" resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.17.1.tgz#8fbe96ca54bfa75262706e044f8f780e904aa45c" @@ -20386,6 +20147,13 @@ npm-run-path@^3.0.0: dependencies: path-key "^3.0.0" +npm-run-path@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.0.tgz#d644ec1bd0569187d2a52909971023a0a58e8438" + integrity sha512-8eyAOAH+bYXFPSnNnKr3J+yoybe8O87Is5rtAQ8qRczJz1ajcsjg8l2oZqP+Ppx15Ii3S1vUTjQN2h4YO2tWWQ== + dependencies: + path-key "^3.0.0" + npmconf@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/npmconf/-/npmconf-2.1.3.tgz#1cbe5dd02e899d365fed7260b54055473f90a15c" @@ -20821,13 +20589,6 @@ optimist@^0.6.1, optimist@~0.6.1: minimist "~0.0.1" wordwrap "~0.0.2" -optimist@~0.3.5: - version "0.3.7" - resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.3.7.tgz#c90941ad59e4273328923074d2cf2e7cbc6ec0d9" - integrity sha1-yQlBrVnkJzMokjB00s8ufLxuwNk= - dependencies: - wordwrap "~0.0.2" - optionator@^0.8.1, optionator@^0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" @@ -21089,23 +20850,11 @@ p-map@^3.0.0: dependencies: aggregate-error "^3.0.0" -p-queue@^2.3.0: - version "2.4.2" - resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-2.4.2.tgz#03609826682b743be9a22dba25051bd46724fc34" - integrity sha512-n8/y+yDJwBjoLQe1GSJbbaYQLTI7QHNZI2+rpmCDbe++WLf9HC3gf6iqj5yfPAV71W4UF3ql5W1+UBPXoXTxng== - p-reduce@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-reduce/-/p-reduce-1.0.0.tgz#18c2b0dd936a4690a529f8231f58a0fdb6a47dfa" integrity sha1-GMKw3ZNqRpClKfgjH1ig/bakffo= -p-retry@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-2.0.0.tgz#b97f1f4d6d81a3c065b2b40107b811e995c1bfba" - integrity sha512-ZbCuzAmiwJ45q4evp/IG9D+5MUllGSUeCWwPt3j/tdYSi1KPkSD+46uqmAA1LhccDhOXv8kYZKNb8x78VflzfA== - dependencies: - retry "^0.12.0" - p-retry@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-3.0.1.tgz#316b4c8893e2c8dc1cfa891f406c4b422bebf328" @@ -21518,7 +21267,7 @@ path-key@^2.0.0, path-key@^2.0.1: resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= -path-key@^3.0.0: +path-key@^3.0.0, path-key@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.0.tgz#99a10d870a803bdd5ee6f0470e58dfcd2f9a54d3" integrity sha512-8cChqz0RP6SHJkMt48FW0A7+qUOn+OsnOsVtzI59tZ8m+5bCSk7hzwET0pulwOM2YMn9J1efb07KB9l9f30SGg== @@ -21870,7 +21619,7 @@ polished@^3.3.1: dependencies: "@babel/runtime" "^7.4.5" -popper.js@^1.14.1, popper.js@^1.14.3, popper.js@^1.14.7: +popper.js@^1.14.1, popper.js@^1.14.7: version "1.15.0" resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.15.0.tgz#5560b99bbad7647e9faa475c6b8056621f5a4ff2" integrity sha512-w010cY1oCUmI+9KwwlWki+r5jxKfTFDVoadl7MSrIujHU5MJ5OR6HTDj6Xo8aoR/QsA56x8jKjA59qGH4ELtrA== @@ -22210,7 +21959,7 @@ prop-types@15.6.1: loose-envify "^1.3.1" object-assign "^4.1.1" -prop-types@15.7.2, prop-types@15.x, prop-types@^15.5.0, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.6, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: +prop-types@15.7.2, prop-types@15.x, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.6, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -22840,14 +22589,6 @@ react-clientside-effect@^1.2.0: "@babel/runtime" "^7.0.0" shallowequal "^1.1.0" -react-clipboard.js@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/react-clipboard.js/-/react-clipboard.js-1.1.3.tgz#86feeb49364553ecd15aea91c75aa142532a60e0" - integrity sha512-97IKPinjiuFIBrCXqhNvKCBJFrSS1mmV5LVALE9djkweau26UWpR5VueYB3Eo3b2vfPtbyt0QUw06YOGdC0rpw== - dependencies: - clipboard "^1.6.1" - prop-types "^15.5.0" - react-color@^2.13.8: version "2.14.1" resolved "https://registry.yarnpkg.com/react-color/-/react-color-2.14.1.tgz#db8ad4f45d81e74896fc2e1c99508927c6d084e0" @@ -23075,7 +22816,7 @@ react-hotkeys@2.0.0-pre4: dependencies: prop-types "^15.6.1" -react-input-autosize@^2.1.2, react-input-autosize@^2.2.1: +react-input-autosize@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-2.2.1.tgz#ec428fa15b1592994fb5f9aa15bb1eb6baf420f8" integrity sha512-3+K4CD13iE4lQQ2WlF8PuV5htfmTRLH6MDnfndHM6LuBRszuXnuyIfE7nhSKt8AzRBZ50bu0sAhkNMeS5pxQQA== @@ -23178,10 +22919,10 @@ react-markdown@^4.0.6: unist-util-visit "^1.3.0" xtend "^4.0.1" -react-moment-proptypes@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/react-moment-proptypes/-/react-moment-proptypes-1.6.0.tgz#8ec266ee392a08ba3412d2df2eebf833ab1046df" - integrity sha512-4h7EuhDMTzQqZ+02KUUO+AVA7PqhbD88yXB740nFpNDyDS/bj9jiPyn2rwr9sa8oDyaE1ByFN9+t5XPyPTmN6g== +react-moment-proptypes@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/react-moment-proptypes/-/react-moment-proptypes-1.7.0.tgz#89881479840a76c13574a86e3bb214c4ba564e7a" + integrity sha512-ZbOn/P4u469WEGAw5hgkS/E+g1YZqdves2BjYsLluJobzUZCtManhjHiZKjniBVT7MSHM6D/iKtRVzlXVv3ikA== dependencies: moment ">=1.6.0" @@ -23267,15 +23008,6 @@ react-reconciler@^0.20.1: prop-types "^15.6.2" scheduler "^0.13.6" -react-redux-request@^1.5.6: - version "1.5.6" - resolved "https://registry.yarnpkg.com/react-redux-request/-/react-redux-request-1.5.6.tgz#8c514dc88264d225e113b4b54a265064e8020651" - integrity sha512-mzdG41GSLwynFI7DII3XNJxkABLD++I3Q1zlZWpcqycWSzWSYkjPUEz7M8r6aIIMzruANHQZX+asulvoaiwFRg== - dependencies: - lodash.get "^4.4.2" - lodash.isequal "^4.5.0" - prop-types "^15.6.1" - react-redux@^5.0.6, react-redux@^5.0.7: version "5.0.7" resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.0.7.tgz#0dc1076d9afb4670f993ffaef44b8f8c1155a4c8" @@ -23393,15 +23125,6 @@ react-router@^4.3.1: prop-types "^15.6.1" warning "^4.0.1" -react-select@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/react-select/-/react-select-1.2.1.tgz#a2fe58a569eb14dcaa6543816260b97e538120d1" - integrity sha512-vaCgT2bEl+uTyE/uKOEgzE5Dc/wLtzhnBvoHCeuLoJWc4WuadN6WQDhoL42DW+TziniZK2Gaqe/wUXydI3NSaQ== - dependencies: - classnames "^2.2.4" - prop-types "^15.5.8" - react-input-autosize "^2.1.2" - react-select@^2.2.0: version "2.4.4" resolved "https://registry.yarnpkg.com/react-select/-/react-select-2.4.4.tgz#ba72468ef1060c7d46fbb862b0748f96491f1f73" @@ -23936,11 +23659,6 @@ redux-saga@^0.16.0: resolved "https://registry.yarnpkg.com/redux-saga/-/redux-saga-0.16.2.tgz#993662e86bc945d8509ac2b8daba3a8c615cc971" integrity sha512-iIjKnRThI5sKPEASpUvySemjzwqwI13e3qP7oLub+FycCRDysLSAOwt958niZW6LhxfmS6Qm1BzbU70w/Koc4w== -redux-test-utils@0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/redux-test-utils/-/redux-test-utils-0.2.2.tgz#593213f30173c5908f72315f08b705e1606094fe" - integrity sha512-+YsUHpzZJ7G85wYgllmGLJ75opIlWrCuKThaVTsHW5xLOrzaLE4abQ3AbYcHkx/vFOReG2D8XUwMfGnFKH8hGw== - redux-thunk@2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.2.0.tgz#e615a16e16b47a19a515766133d1e3e99b7852e5" @@ -24721,7 +24439,7 @@ rimraf@2.6.3, rimraf@^2.6.3, rimraf@~2.6.2: dependencies: glob "^7.1.3" -rimraf@2.7.1, rimraf@^2.7.1: +rimraf@^2.7.1: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== @@ -24818,11 +24536,6 @@ rsvp@^4.8.4: resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== -rsync@0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/rsync/-/rsync-0.6.1.tgz#3681a0098bd8750448f8bf9da1fee09f7763742b" - integrity sha1-NoGgCYvYdQRI+L+dof7gn3djdCs= - run-async@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/run-async/-/run-async-0.1.0.tgz#c8ad4a5e110661e402a7d21b530e009f25f8e389" @@ -25058,7 +24771,7 @@ sass-resources-loader@^2.0.1: glob "^7.1.1" loader-utils "^1.0.4" -sax@>=0.6.0, sax@^1.2.1, sax@^1.2.4, sax@~1.2.1, sax@~1.2.4: +sax@>=0.6.0, sax@^1.2.1, sax@^1.2.4, sax@~1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== @@ -25466,11 +25179,23 @@ shebang-command@^1.2.0: dependencies: shebang-regex "^1.0.0" +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + shebang-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + shell-quote@1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.6.1.tgz#f4781949cce402697127430ea3b3c5476f481767" @@ -26247,11 +25972,6 @@ stream-spigot@~2.1.2: dependencies: readable-stream "~1.1.0" -stream-stream@^1.2.6: - version "1.2.6" - resolved "https://registry.yarnpkg.com/stream-stream/-/stream-stream-1.2.6.tgz#a9ae071c64c11b8584f52973f7715e37e5144c43" - integrity sha1-qa4HHGTBG4WE9Slz93FeN+UUTEM= - stream-to-observable@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/stream-to-observable/-/stream-to-observable-0.1.0.tgz#45bf1d9f2d7dc09bed81f1c307c430e68b84cffe" @@ -26760,19 +26480,6 @@ svg-to-pdfkit@^0.1.7: dependencies: pdfkit ">=0.8.1" -svgo@^0.7.2: - version "0.7.2" - resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5" - integrity sha1-n1dyQTlSE1xv779Ar+ak+qiLS7U= - dependencies: - coa "~1.0.1" - colors "~1.1.2" - csso "~2.3.1" - js-yaml "~3.7.0" - mkdirp "~0.5.1" - sax "~1.2.1" - whet.extend "~0.9.9" - svgo@^1.2.1: version "1.2.2" resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.2.2.tgz#0253d34eccf2aed4ad4f283e11ee75198f9d7316" @@ -27596,25 +27303,6 @@ trough@^1.0.0: dependencies: glob "^6.0.4" -trunc-html@1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/trunc-html/-/trunc-html-1.1.2.tgz#1e97d51f67d470b67662b1a670e6d0ea7a8edafe" - integrity sha1-HpfVH2fUcLZ2YrGmcObQ6nqO2v4= - dependencies: - assignment "2.2.0" - insane "2.6.1" - trunc-text "1.0.1" - -trunc-text@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/trunc-text/-/trunc-text-1.0.1.tgz#58f876d8ac59b224b79834bb478b8656e69622b5" - integrity sha1-WPh22KxZsiS3mDS7R4uGVuaWIrU= - -trunc-text@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/trunc-text/-/trunc-text-1.0.2.tgz#b582bb3ddea9c9adc25017d737c48ebdd2157406" - integrity sha1-tYK7Pd6pya3CUBfXN8SOvdIVdAY= - ts-debounce@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/ts-debounce/-/ts-debounce-1.0.0.tgz#e433301744ba75fe25466f7f23e1382c646aae6a" @@ -29944,11 +29632,6 @@ whatwg-url@^7.0.0: tr46 "^1.0.1" webidl-conversions "^4.0.2" -whet.extend@~0.9.9: - version "0.9.9" - resolved "https://registry.yarnpkg.com/whet.extend/-/whet.extend-0.9.9.tgz#f877d5bf648c97e5aa542fadc16d6a259b9c11a1" - integrity sha1-+HfVv2SMl+WqVC+twW1qJZucEaE= - which-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" @@ -29973,6 +29656,13 @@ which@^1.1.1, which@^1.2.1, which@^1.2.14, which@^1.2.8, which@^1.3.0: dependencies: isexe "^2.0.0" +which@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.1.tgz#f1cf94d07a8e571b6ff006aeb91d0300c47ef0a4" + integrity sha512-N7GBZOTswtB9lkQBZA4+zAXrjEIWAUOB93AvzUiudRzRxhUdLURQ7D/gAIMY1gatT/LTbmbcv8SiYazy3eYB7w== + dependencies: + isexe "^2.0.0" + wide-align@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" @@ -30237,13 +29927,6 @@ write@^0.2.1: dependencies: mkdirp "^0.5.1" -ws@^5.2.0: - version "5.2.2" - resolved "https://registry.yarnpkg.com/ws/-/ws-5.2.2.tgz#dffef14866b8e8dc9133582514d1befaf96e980f" - integrity sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA== - dependencies: - async-limiter "~1.0.0" - ws@^6.1.0: version "6.2.0" resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.0.tgz#13806d9913b2a5f3cbb9ba47b563c002cbc7c526"