diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index f515b254b868..0ba694cbf213 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -12,6 +12,17 @@ jobs: contents: write pull-requests: write name: Backport + # Only react to merged PRs for security reasons. + # See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target. + if: > + github.event.pull_request.merged + && ( + github.event.action == 'closed' + || ( + github.event.action == 'labeled' + && contains(github.event.label.name, 'backport') + ) + ) steps: - name: GitHub App token id: github_app_token @@ -22,10 +33,9 @@ jobs: # opensearch-trigger-bot installation ID installation_id: 22958780 - # Using fork of https://github.com/tibdex/backport - # https://github.com/tibdex/backport/pull/81 - name: Backport - uses: VachaShah/backport@v1.1.4 + uses: VachaShah/backport@v2.1.0 with: github_token: ${{ steps.github_app_token.outputs.token }} - branch_name: backport/backport-${{ github.event.number }} + head_template: backport/backport-<%= number %>-to-<%= base %> + files_to_skip: "CHANGELOG.md" diff --git a/.github/workflows/cypress_workflow.yml b/.github/workflows/cypress_workflow.yml index afe33c85ce17..41b9d46aa32f 100644 --- a/.github/workflows/cypress_workflow.yml +++ b/.github/workflows/cypress_workflow.yml @@ -11,7 +11,8 @@ env: FTR_PATH: 'ftr' START_CMD: 'node ../scripts/opensearch_dashboards --dev --no-base-path --no-watch' OPENSEARCH_SNAPSHOT_CMD: 'node ../scripts/opensearch snapshot' - SPEC: 'cypress/integration/core-opensearch-dashboards/opensearch-dashboards/*.js,' + SPEC: 'cypress/integration/core-opensearch-dashboards/opensearch-dashboards/**/*.js,' + CYPRESS_ENV: 'env CYPRESS_VISBUILDER_ENABLED=true ' jobs: cypress-tests: @@ -75,7 +76,7 @@ jobs: working-directory: ${{ env.FTR_PATH }} start: ${{ env.OPENSEARCH_SNAPSHOT_CMD }}, ${{ env.START_CMD }} wait-on: 'http://localhost:9200, http://localhost:5601' - command: yarn cypress:run-without-security --browser chromium --spec ${{ env.SPEC }} + command: ${{ env.CYPRESS_ENV }} yarn cypress:run-without-security --browser chromium --spec ${{ env.SPEC }} # Screenshots are only captured on failure, will change this once we do visual regression tests - uses: actions/upload-artifact@v3 diff --git a/CHANGELOG.md b/CHANGELOG.md index e859e023e260..4c656898be70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ### 🛡 Security +- [Legacy Maps Plugin] Prevent reverse-tabnabbing ([#2540](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2540)) +- Eliminate dependency on `got` versions older than 11.8.5 ([#2801](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2801)) +- [Multi DataSource] Add explicit no spellcheck on password fields ([#2818](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2818)) + ### 📈 Features/Enhancements - [MD] Support legacy client for data source ([#2204](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2204)) @@ -32,11 +36,15 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - [Vis Builder] Rename wizard to visBuilder in i18n id and formatted message id ([#2635](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2635)) - [Vis Builder] Rename wizard to visBuilder in class name, type name and function name ([#2639](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2639)) - [Vis Builder] Rename wizard on save modal and visualization table ([#2645](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2645)) +- [Vis Builder] Adds functional tests to CI ([#2728](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2728)) - [Vis Builder] Enable VisBuilder by default ([#2725](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2725)) - Change save object type, wizard id and name to visBuilder #2673 ([#2673](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2673)) - [Multi DataSource] Update MD data source documentation link ([#2693](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2693)) - [Save Object Aggregation View] Add extension point in saved object management to register namespaces and show filter ([#2656](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2656)) - [Save Object Aggregation View] Fix for export all after scroll count response changed in PR#2656 ([#2696](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2696)) +- [Vis Builder] Add an experimental table visualization in vis builder ([#2705](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2705)) +- [Vis Builder] Add field summary popovers ([#2682](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2682)) +- Add yarn opensearch arg to setup plugin dependencies ([#2544](https://github.com/opensearch-project/OpenSearch-Dashboards/issues/2544)) ### 🐛 Bug Fixes @@ -44,7 +52,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - [Vis Builder] Fixes visualization shift when editing agg ([2401](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2401)) - [Vis Builder] Renames "Histogram" to "Bar" in vis type picker ([2401](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2401)) - [Vis Builder] Update vislib params and misc fixes ([2610](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2610)) -* [Vis Builder] Bug fixes for datasource picker and auto time interval ([2632](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2632)) +- [Vis Builder] Bug fixes for datasource picker and auto time interval ([2632](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2632)) - [MD] Add data source param to low-level search call in Discover ([#2431](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2431)) - [Multi DataSource] Skip data source view in index pattern step when pick default ([#2574](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2574)) - [Multi DataSource] Address UX comments on Edit Data source page ([#2629](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2629)) @@ -52,21 +60,36 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - [Multi DataSource] Address UX comments on index pattern management stack ([#2611](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2611)) - [Multi DataSource] Apply get indices error handling in step index pattern ([#2652](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2652)) - [Vis Builder] Last Updated Timestamp for visbuilder savedobject is getting Generated ([#2628](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2628)) -- Removed Leftover X Pack references ([#2638](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2638)) +- Removed Leftover X Pack references ([#2638](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2638)) +- Removes Add Integration button ([#2723](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2723)) +- Change geckodriver version to make consistency ([#2772](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2772)) +- [Multi DataSource] Update default audit log path ([#2793](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2793)) +- [Table Visualization] Fix first column sort issue ([#2828](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2828)) +- Temporary workaround for task-kill exceptions on Windows when it is passed a pid for a process that is already dead ([#2842](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2842)) +- [Vis Builder] Fix empty workspace animation does not work in firefox ([#2853](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2853)) +- Bumped `del` version to fix MacOS race condition ([#2847](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2873)) ### 🚞 Infrastructure - Add CHANGELOG.md and related workflows ([#2414](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2414)) +- Update backport custom branch name to utilize head template ([#2766](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2766)) ### 📝 Documentation -* [MD] Add design documents of multiple data source feature [#2538](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2538) +- Add the release runbook to RELEASING.md ([#2533](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2533)) +- [MD] Add design documents of multiple data source feature [#2538](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2538) +- [MD] Tweak multiple data source design doc [#2724](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2724) +- Add `current-usage.md` and more details to `README.md` of `charts` plugin ([#2695](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2695)) + ### 🛠 Maintenance - Adding @zhongnansu as maintainer. ([#2590](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2590)) ### 🪛 Refactoring -* [MD] Refactor data source error handling ([#2661](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2661)) + +- [MD] Refactor data source error handling ([#2661](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2661)) +- Refactor and improve Discover field summaries ([#2391](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2391)) +- [Vis Builder] Removed Hard Coded Strings and Used i18n to transalte([#2867](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2867)) ### 🔩 Tests @@ -87,6 +110,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - Resolve sub-dependent d3-color version and potential security issue ([#2454](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2454)) - [CVE-2022-3517] Bumps minimatch from 3.0.4 to 3.0.5 and [IBM X-Force ID: 220063] unset-value from 1.0.1 to 2.0.1 ([#2640](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2640)) - [CVE-2022-37601] Bump loader-utils to 2.0.3 ([#2689](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2689)) +- [WS-2021-0638][Security] bump mocha to 10.1.0 ([#2711](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2711)) ### 📈 Features/Enhancements diff --git a/DEVELOPER_GUIDE.md b/DEVELOPER_GUIDE.md index 4dc3e0d2583e..4fe8fc4e368c 100644 --- a/DEVELOPER_GUIDE.md +++ b/DEVELOPER_GUIDE.md @@ -59,6 +59,30 @@ Dashboards. In a separate terminal you can run the latest snapshot built using: $ yarn opensearch snapshot ``` +If you would like to download a specific OpenSearch plugin on the cluster snapshot, pass the `--P` flag after `yarn opensearch snapshot`. We can use the flag multiple times to install multiple plugins on the cluster snapshot. The argument value can be URL to the plugin's zip file, maven coordinates of the plugin or for local zip files, use `file:` followed by the absolute or relative path to the plugin zip file. Below is the example help command: + +``` +$ yarn opensearch snapshot --P https://repo1.maven.org/maven2/org/opensearch/plugin/opensearch-test-plugin/2.4.0.0/opensearch-test-plugin-2.4.0.0.zip +``` + +Following are the list of options that can be passed after `yarn opensearch snapshot` to configure the cluster snapshot. +Options: + + --license Run with a 'oss', 'basic', or 'trial' license [default: oss] + --version Version of OpenSearch to download [default: 3.0.0}] + --base-path Path containing cache/installations [default: /home/ubuntu/OpenSearch-Dashboards/.opensearch] + --install-path Installation path, defaults to 'source' within base-path + --data-archive Path to zip or tarball containing an OpenSearch data directory to seed the cluster with. + --password Sets password for opensearch user [default: changeme] + -E Additional key=value settings to pass to OpenSearch + --download-only Download the snapshot but don't actually start it + --ssl Sets up SSL on OpenSearch + --P OpenSearch plugin artifact URL to install it on the cluster. + +``` +$ yarn opensearch snapshot --version 2.2.0 -E cluster.name=test -E path.data=/tmp/opensearch-data --P org.opensearch.plugin:test-plugin:2.2.0.0 --P file:/home/user/opensearch-test-plugin-2.2.0.0.zip +``` + **Warning:** Starting the Dashboards instance before or during the initialization of the OpenSearch Server can cause Dashboards to sometimes misbehave. Ensure that the OpenSearch server instance is up and running first before starting up the Dashboards dev server from the next step. ### Run OpenSearch Dashboards diff --git a/RELEASING.md b/RELEASING.md index 50bb965b8d55..6be9bfc2b9ed 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -1,3 +1,76 @@ ## Releasing -This project follows [OpenSearch project branching, labelling, and releasing](https://github.com/opensearch-project/.github/blob/main/RELEASING.md). \ No newline at end of file +This project follows [OpenSearch project branching, labelling, and releasing](https://github.com/opensearch-project/.github/blob/main/RELEASING.md). + +## Runbook + +### Overview + +The OpenSearch project releases versioned distributions of OpenSearch, OpenSearch Dashboards, and the OpenSearch plugins. This runbook details the steps involved in performing major, minor, and patch version releases for the OpenSearch Dashboards project; these steps need to be completed by the release managers (RM) assigned to each release. The RM is also responsible for updating the release status on the release tracking issues maintained on GitHub ([example](https://github.com/opensearch-project/OpenSearch-Dashboards/issues/2230)). + +\*Important Dates: https://opensearch.org/releases.html + +### Release Phase 1 - Preparation + +For major and minor releases, the OpenSearch build repository [maintainers](https://github.com/opensearch-project/opensearch-build/blob/main/MAINTAINERS.md) will create a release issue in the OpenSearch Dashboards repository ([example](https://github.com/opensearch-project/OpenSearch-Dashboards/issues/2230)) which links to the overall issue in the OpenSearch build repository ([example](https://github.com/opensearch-project/opensearch-build/issues/2447)). For patch releases, they will only create the overall issue in the OpenSearch build repository ([example](https://github.com/opensearch-project/opensearch-build/issues/2650)). + +The OpenSearch Dashboards release issue will be assigned to the RM who should prepare for the release by reviewing all listed tasks. They should also compare the current release issue to the issue of the previous release version to ensure that all new processes have been captured. + +The RM should review the [public roadmap](https://github.com/orgs/opensearch-project/projects/1) and confirm the release scope with other OpenSearch Dashboards [maintainers](https://github.com/opensearch-project/OpenSearch-Dashboards/blob/main/MAINTAINERS.md) as well as the feature owners. Since release labels are intended to highlight the features and fixes meant for the upcoming release, the RM should verify that all issues and pull requests are labelled accordingly. For example, if current release version was v2.3.0, all features not ready for the release should be labeled as v2.4.0 or later by discussing with the feature owners. The RM should also check all PRs for the current release version to confirm that they are merged into the `main` branch and their backported PRs are merged with all CI passing. + +#### How to validate that merged commits have been properly backported? + +1. For any PRs merged to main, make sure it has backport labels ([example - Add v2.3.0 release notes](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2318)) +2. Backport PR is generated automatically by [opensearch-trigger-bot](https://github.com/apps/opensearch-trigger-bot) + ([example - [Backport 2.3] Add v2.3.0 release notes](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2333)) +3. If the backport PR is not generated by opensearch-trigger-bot after 24 hours, create it manually following this [instruction](https://github.com/opensearch-project/.github/blob/main/RELEASING.md#backporting) +4. Ensure that all CI passed and it has two approvals. Then merge the PR. + +#### Prepare BWC data and update BWC versions + +Backward Compatibility Tests (BWC) are cypress tests that identify any obvious compatibility bugs with previous versions. The RM should generate test data and test locally following instructions [here](https://github.com/opensearch-project/OpenSearch-Dashboards/blob/main/TESTING.md#backwards-compatibility-tests) and cut PR to include both generated data and version upgrade for automated build. (See example [PR](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2393/files)) + +#### Cut release branch for major / minor release + +For major / minor release, the RM should cut the release branch from the parent branch, [following OpenSearch project branching](https://github.com/opensearch-project/.github/blob/main/RELEASING.md#opensearch-branching) + +### Release Phase 2 - Pre-Release + +The release process for OpenSearch is centralized. Jenkins workflows are in place to regularly find differences in the OpenSearch and OpenSearch Dashboards components and create new snapshots for those that have been updated. The RM should update the release branch version in the distribution manifest (see example [PR](https://github.com/opensearch-project/opensearch-build/pull/2586/files)) and increment the parent branch version (see example [PR](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2295/files)). + +#### Write release notes + +OpenSearch Dashboards maintains a [CHANGELOG.md](https://github.com/opensearch-project/OpenSearch-Dashboards/blob/main/CHANGELOG.md) and verifies it as part of the PR checklist step. For the time being, the RM should create release notes PR with the label `doc`, referring to the `CHANGELOG.md` (see example [PR](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/2318)) + +### Release Phase 3 - Release testing + +#### Verify integration and BWC test results + +The automated integration test and BWC test are executed concurrently with the release artifacts build. The RM should examine the test results and assist in triaging the broken test case. + +Example build: + +x64: https://build.ci.opensearch.org/job/integ-test-opensearch-dashboards/995/ + +arm64: https://build.ci.opensearch.org/job/integ-test-opensearch-dashboards/996/ + +Example test results: +https://opensearch-project.github.io/opensearch-dashboards-functional-test/site/?version=2.3.0&build_number=4104&test_number=996 + +Note: change `arch` to match the operating system CPU architecture for which the build is being produced. + +#### Sanity test with tarball and docker image + +Once the release candidate artifacts are built, the RM should configure the OpenSearch cluster with OpenSearch Dashboards according to the [instructions in the OpenSearch build repo](https://github.com/opensearch-project/opensearch-build/issues/2447#issuecomment-1241406594) and produce sanity tests to identify broken functionalities caused by new features / code changes. If you find any, please file bug reports and assist in triaging the bugfix. + +### Release Phase 4 - Release Announcement + +Release artifacts and announcements will be available on https://opensearch.org/releases.html. Any website documentation changes will require a PR on the [OpenSearch documentation-website repo.](https://github.com/opensearch-project/documentation-website) + +### Release Phase 5 - Post-Release + +After a release is announced, OpenSearch build repository maintainers will trigger a job that creates a tag in each repository based on the commit hash and branch that was included in the release; the release tag could take a few hours to show up on GitHub. + +The RM should update the [release page](https://github.com/opensearch-project/OpenSearch-Dashboards/releases/) with the latest download URL and release notes after the release tag is created. + +If needed, the RM could conduct a retrospective review of the release, and publish their findings regarding any missed steps and process improvements. diff --git a/SECURITY.md b/SECURITY.md index 0b85ca04ed26..f450e11235ba 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,3 +1,27 @@ ## Reporting a Vulnerability -If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/) or directly via email to aws-security@amazon.com. Please do **not** create a public GitHub issue. \ No newline at end of file +- If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/) or directly via email to aws-security@amazon.com. Please do **not** create a public GitHub issue. + +- For Security-CVE related fix - + - For direct dependency - Use ```yarn upgrade package``` to update the package and in order to enforce as sub-deps please add nested-dep step2. + + - For nested dependency/sub-deps - In order to enforce package above Vx.y.z, we can add version in the resolutions [section](https://classic.yarnpkg.com/lang/en/docs/selective-version-resolutions/) for all the package sub-deps or specific package sub-dep. For more on version updates please see +[Why](https://classic.yarnpkg.com/lang/en/docs/selective-version-resolutions/#toc-why-would-you-want-to-do-this) and [How](https://classic.yarnpkg.com/lang/en/docs/selective-version-resolutions/#toc-how-to-use-it) to upgrade. + - To add the CVEs fix to previous versions, add label ex: backport 1.x. + + ``` + Example: foobar@1.x vulnerable package and 1.y is the fix + step 1: + For direct dependency checks: + run: yarn upgrade foobar@1.y to update the package.json + and yarn install to update the yarn.lock file + Step 2. + Check for sub deps foobar in other package. + If foobar@1.x exists for subdeps in yarn.lock file + Then edit the package.json file and add **/foobar@1.y in resolution section as shown below to enforce the 1.y. + 'resolutions': { "**/foobar": "^1.y", + "**/foo": "^2.x" , + "**/bar": "^3.k"} + Then run: yarn install for updating yarn.lock file + + \ No newline at end of file diff --git a/docs/charts/current_usage.md b/docs/charts/current_usage.md new file mode 100644 index 000000000000..f3e7db435955 --- /dev/null +++ b/docs/charts/current_usage.md @@ -0,0 +1,184 @@ +# Usage + +The purpose of this doc is to keep track of the current (as of 2022-11-14) usage of the [charts plugin](../../src/plugins/charts/), as well as other packages and tools with similar purposes. See https://github.com/opensearch-project/OpenSearch-Dashboards/issues/2830 for more information on future plans. + +## Lifecycle methods/services + +### `ChartsPluginStart` + +1. `ChartsPluginStart['theme']` used by `discover` plugin to fetch `chartsTheme` and `chartsBaseTheme` for use in styling the histogram. + +### `ChartsPluginSetup` + +1. `ChartsPluginSetup` declared as one of `MetricVisPluginSetupDependencies` in the `vis_type_metric` plugin, but isn't actually used. +2. `ChartsPluginSetup['colors']` used by `vis_type_tagcloud` plugin. Only the seed colors are used via `d3.scale.ordinal().range(colors.seedColors)` +3. `ChartsPluginSetup.colors` and `ChartsPluginSetup.theme` used by the `vis_type_timeseries` plugin. + 1. `themeService.useChartsBaseTheme()` is used only as a fallback; otherwise theme (dark or light) is calculated from the user-specified background color + 2. `colors.mappedColors` used to fetch mapped colors only if user has not specified a color for a particular series label (and there's no color specified from the server). +4. `ChartsPluginSetup.colors.createColorLookupFunction()` is used by the `vis_type_vislib` plugin, ultimately, as part of `getColorFunc()` and `getPieColorFunc()`; the former also uses fallback for default and overwritten colors from `uiState`. +5. Set as a dependency in stub plugin `vis_type_xy`, but not actually used. + +## `uiSettings` in advanced settings `visualization:colorMapping` + +Appears to only be used by the telemetry plugin: https://github.com/opensearch-project/OpenSearch-Dashboards/blob/95f4fd5c6a6cd59bd555bf0ec120843ef6a93566/src/plugins/telemetry/schema/oss_plugins.json#L1363 + +## Static functions and components + +### Color Maps + +#### `ColorMap` interface + +1. `region_map` plugin +2. `tile_map` plugin +3. `timeline` plugin +4. `vis_type_metric` plugin +5. `vis_type_timeline` plugin + +#### `ColorSchema` interface + +1. `maps_legacy` plugin +2. `region_map` plugin +3. `tile_map` plugin +4. `vis_type_metric` plugin +5. `vis_type_vislib` plugin +6. `visualizations` plugin +7. `visualize` plugin +8. `vis_builder` plugin + +#### `ColorSchemas` enum + +1. `region_map` plugin +2. `tile_map` plugin +3. `vis_type_metric` plugin +4. `vis_type_vislib` plugin +5. `vis_builder` plugin + +#### `RawColorSchema` interface + +Not used by any core plugins + +#### `colorSchemas` array of objects + +1. `region_map` plugin +2. `tile_map` plugin +3. `vis_type_metric` plugin +4. `vis_type_vislib` plugin +5. `vis_builder` plugin + +#### `getHeatmapColors` function + +1. `vis_type_metric` plugin +2. `vis_type_vislib` plugin + +#### `truncatedColorMaps` object + +1. `region_map` plugin +2. `tile_map` plugin + +#### `truncatedColorSchemas` array of objects + +1. `region_map` plugin +2. `tile_map` plugin + +#### `vislibColorMaps` object + +1. `vis_type_metric` plugin + +### React components + +These components may eventually make more sense elsewhere. See https://github.com/opensearch-project/OpenSearch-Dashboards/issues/2832 for one such proposal. + +#### `ColorModes` object + +1. `vis_type_metric` plugin +2. `vis_type_vislib` plugin +3. `vis_builder` plugin + +#### `Rotates` object + +1. `vis_type_vislib` plugin + +#### `BasicOptions` component + +1. `tile_map` plugin +2. `vis_type_vislib` plugin +3. `vis_builder` plugin + +#### `ColorRanges` component + +1. `vis_type_metric` plugin +2. `vis_type_vislib` plugin +3. `vis_builder` plugin + +#### `ColorSchemaOptions` component + +Accounts for customized `vis.colors` in the `uiState`. Supports setting custom colors via legend, and resetting. + +1. `vis_type_metric` plugin. Doesn't actually support custom colors +2. `vis_type_vislib` plugin +3. `vis_builder` plugin (metric visualization). Doesn't support custom colors + +#### `NumberInputOption` component + +1. `region_map` plugin +2. `vis_type_table` plugin +3. `vis_type_vislib` plugin + +#### `RangeOption` component + +1. `tile_map` plugin +2. `vis_type_markdown` plugin +3. `vis_type_metric` plugin +4. `vis_type_timeseries` plugin +5. `vis_builder` plugin + +#### `RequiredNumberInputOption` component + +1. `vis_type_vislib` plugin + +#### `SelectOption` component + +1. `index_pattern_management` plugin +2. `maps_legacy` plugin +3. `region_map` plugin +4. `tile_map` plugin +5. `vis_type_table` plugin +6. `vis_type_tagcloud` plugin +7. `vis_type_timeseries` plugin +8. `vis_type_vislib` plugin +9. `vis_builder` plugin + +#### `SwitchOption` component + +1. `maps_legacy` plugin +2. `region_map` plugin +3. `tile_map` plugin +4. `vis_type_markdown` plugin +5. `vis_type_metric` plugin +6. `vis_type_table` plugin +7. `vis_type_tagcloud` plugin +8. `vis_type_vislib` plugin +9. `vis_builder` plugin + +#### `TextInputOption` component + +1. `maps_legacy` plugin +2. `vis_type_vislib` plugin + +# OUI chart colors + +An alternative to using color schemas and maps provided by the `charts` plugin is to use [color palettes from OUI](https://github.com/opensearch-project/oui/blob/main/src/services/color/oui_palettes.ts). + +## `ouiPaletteColorBlind()` + +1. `index_pattern_management` plugin +2. `vis_type_vega` plugin +3. `vis_type_vislib` plugin + +## Other quantitative palettes + +Not currently used + +## `colorPalette` + +Not currently used diff --git a/docs/multi-datasource/client_management_design.md b/docs/multi-datasource/client_management_design.md index 389e2e408255..c83b254fe289 100644 --- a/docs/multi-datasource/client_management_design.md +++ b/docs/multi-datasource/client_management_design.md @@ -39,8 +39,8 @@ This design is part of the OpenSearch Dashboards multi data source project [[RFC ### 4.0 Answer some critical design questions -**1.** **How to set up connection(clients) for different datasources?** -Similar to how current OpenSearch Dashboards talks to default OS by creating opensearch node.js client using [opensearch-js](https://github.com/opensearch-project/opensearch-js) library, for datasources we also create clients for each. Critical params that differentiate data sources are `url` and `auth` +**1.** **How to set up connection(clients) for different data sources?** +Similar to how current OpenSearch Dashboards talks to default OpenSearch by creating a client using [opensearch-js](https://github.com/opensearch-project/opensearch-js) library, for data sources we also create clients for each connection. Critical params that differentiate data sources are `url` and `auth` ```ts const { Client } = require('@opensearch-project/opensearch'); @@ -59,7 +59,7 @@ dataSourceClient.ping(); ``` **2. How to expose datasource clients to callers through clean interfaces?** -We create a `data source service`. Similar to existing `opensearch service` in core, which provides client of default OS cluster. This new service will be dedicated to provide clients for data sources. Following the same paradigm we can register this new service to `CoreStart`, `CoreRouteHandlerContext` , in order to expose data source client to plugins and modules. The interface is exposed from new service, and thus it doesn’t mess up with any existing services, and keeps the interface clean. +We create a `data source service`. Similar to existing `opensearch service` in core, which provides client of default OpenSearch cluster. This new service will be dedicated to provide clients for data sources. Following the same paradigm we can register this new service to `CoreStart`, `CoreRouteHandlerContext` , in order to expose data source client to plugins and modules. The interface is exposed from new service, and thus it doesn’t mess up with any existing services, and keeps the interface clean. ``` *// Existing* @@ -77,14 +77,14 @@ The context is that user can only turn on/off multiple datasource feature by upd **4.How to manage multiple clients/connection efficiently, and not consume all the memory?** -- For datasources with different endpoint, user client Pooling (E.g. LRU cache) +- For data sources with different endpoint, user client Pooling (E.g. LRU cache) - For data sources with same endpoint, but different user, use connection pooling strategy (child client) provided by opensearch-js. **5.Where should we implement the core logic?** Current `opensearch service` exists in core. The module we'll implement has similarity function wise, but we choose to implement `data source service` in plugin along with `crypto` service for the following reasons. -1. Data source is a feature that can be turned on or off. Plugin is born for such plugable use case. -2. We don't mess up with OpenSearch Dashboards core, since this is an experimental feature, the potential risk of breaking existing behavior will be lower if we use plugin. Worst case, user could just uninstall the plugin. +1. Data source is a feature that can be turned on or off. Plugin is born for such pluggable use case. +2. We don't mess up with OpenSearch Dashboards core, since this is an experimental feature, the potential risk of breaking existing behavior will be lowered if we use plugin. Worst case, user could just uninstall the plugin. 3. Complexity wise, it's about the same amount of work. ### 4.1 Data Source Plugin @@ -130,14 +130,14 @@ We need to configure the data source client by either creating a new one, or loo } ``` -- Get root client: Look up client Pool by **endpoint**, return client if existed. If misses, we create new client instance and load into pool. At this step, the client won't have any auth info. +- Get root client: Look up the client pool by **endpoint** and return the client if it exists. If a client was not found, a new client instance is created and loaded into pool. At this step, the client won't have any auth info. - Get credentials: Call crypto service utilities to **decrypt** user credentials from `DataSource` Object. -- Assemble the actual query client: With auth info and root client, we’ll leverage the openearch-js connection pooling strategy to create the actual query client from root client by `client.child()`. +- Assemble the actual query client: With auth info and root client, we’ll leverage the `opensearch-js` connection pooling strategy to create the actual query client from root client by `client.child()`. #### 4.2.1 Legacy Client -OpenSearch Dashboards is forked from Kibana 7.10. At the time of the fork happened, there are 2 types of client used in the codebase. One is the new client, which later was migrated as `opensearhc-js`, the other one is the legacy client which is `elasticsearc-js`. Legacy clients are still used many critical features, such as visualization, index pattern management, along with new client. +OpenSearch Dashboards had two types of clients available for use when created. One was the "new client" which has since been separated into `opensearch-js`, and the other was the legacy client named `elasticsearch-js`. Legacy clients are still used by some core features like visualization and index pattern management. ```ts // legacy client @@ -174,7 +174,7 @@ This is for plugin to access data source client via request handler. For example ### 4.4 Refactor data plugin search module to call core API to get datasource client -`Search strategy` is the low level API of data plugin search module. It retrieve clients and query OpenSearch. It needs to be refactored to switch between default client and datasource client, depending on whether a request is send to datasource or not. +`Search strategy` is the low level API of data plugin search module. It retrieves clients and queries OpenSearch. It needs to be refactored to switch between the default client and the datasource client, depending on whether or not a request is sent to the datasource. Currently default client is retrieved by search module of data plugin to interact with OpenSearch by this API call. Ref: [opensearch-search-strategy.ts](https://github.com/opensearch-project/opensearch-dashboards/blob/e3b34df1dea59a253884f6da4e49c3e717d362c9/src/plugins/data/server/search/opensearch_search/opensearch_search_strategy.ts#L75) @@ -184,7 +184,7 @@ const client: OpenSearchClient = core.opensearch.client.asCurrentUser; client.search(params); ``` -Similarly we’ll have the following for datasource use case. `AsCurrentUser` is something doesn’t make sense for datasource, because it’s always the “current” user credential defined in the “datasource”, that we are using to create the client, or look up the client pool. +Similarly we’ll have the following for datasource use case. `AsCurrentUser` doesn't really apply to a datasource because it’s always the “current” user's credentials, defined in the “datasource”, that gets used to initialize the client or lookup the client pool. ```ts if (request.dataSource) { diff --git a/package.json b/package.json index 437412617326..e41ae70451e7 100644 --- a/package.json +++ b/package.json @@ -161,7 +161,7 @@ "commander": "^3.0.2", "core-js": "^3.6.5", "deep-freeze-strict": "^1.1.1", - "del": "^5.1.0", + "del": "^6.1.1", "dns-sync": "^0.2.1", "elastic-apm-node": "^3.7.0", "elasticsearch": "^16.7.0", @@ -377,7 +377,7 @@ "exit-hook": "^2.2.0", "fetch-mock": "^7.3.9", "fp-ts": "^2.3.1", - "geckodriver": "^3.0.1", + "geckodriver": "^3.0.2", "getopts": "^2.2.5", "grunt": "^1.5.2", "grunt-available-tasks": "^0.6.3", @@ -408,7 +408,7 @@ "load-grunt-config": "^4.0.1", "load-json-file": "^6.2.0", "markdown-it": "^12.3.2", - "mocha": "^7.2.0", + "mocha": "10.1.0", "mock-fs": "^4.12.0", "monaco-editor": "~0.17.0", "ms-chromium-edge-driver": "^0.4.3", diff --git a/packages/osd-i18n/package.json b/packages/osd-i18n/package.json index e3acaba58604..98b3f9bbde1d 100644 --- a/packages/osd-i18n/package.json +++ b/packages/osd-i18n/package.json @@ -18,7 +18,7 @@ "@osd/dev-utils": "1.0.0", "@types/intl-relativeformat": "^2.1.0", "@types/react-intl": "^2.3.15", - "del": "^5.1.0", + "del": "^6.1.1", "getopts": "^2.2.5", "supports-color": "^7.0.0", "typescript": "4.0.2" diff --git a/packages/osd-interpreter/package.json b/packages/osd-interpreter/package.json index c8de5349c64f..4654b73b186e 100644 --- a/packages/osd-interpreter/package.json +++ b/packages/osd-interpreter/package.json @@ -24,7 +24,7 @@ "babel-loader": "^8.2.3", "copy-webpack-plugin": "^6.0.2", "css-loader": "^5.2.7", - "del": "^5.1.0", + "del": "^6.1.1", "getopts": "^2.2.5", "pegjs": "0.10.0", "sass-loader": "^10.2.0", diff --git a/packages/osd-monaco/package.json b/packages/osd-monaco/package.json index 8754663f891c..e937b1e8f60b 100644 --- a/packages/osd-monaco/package.json +++ b/packages/osd-monaco/package.json @@ -17,7 +17,7 @@ "@osd/dev-utils": "1.0.0", "babel-loader": "^8.2.3", "css-loader": "^5.2.7", - "del": "^5.1.0", + "del": "^6.1.1", "raw-loader": "^4.0.2", "supports-color": "^7.0.0", "typescript": "4.0.2", diff --git a/packages/osd-opensearch/package.json b/packages/osd-opensearch/package.json index d086033f7288..c2e52d8230a8 100644 --- a/packages/osd-opensearch/package.json +++ b/packages/osd-opensearch/package.json @@ -17,7 +17,7 @@ "abort-controller": "^3.0.0", "chalk": "^4.1.0", "dedent": "^0.7.0", - "del": "^5.1.0", + "del": "^6.1.1", "execa": "^4.0.2", "getopts": "^2.2.5", "glob": "^7.1.7", @@ -30,6 +30,6 @@ "devDependencies": { "@osd/babel-preset": "1.0.0", "@babel/cli": "^7.16.0", - "del": "^5.1.0" + "del": "^6.1.1" } } diff --git a/packages/osd-opensearch/src/cli_commands/snapshot.js b/packages/osd-opensearch/src/cli_commands/snapshot.js index 99b85b7439a9..3cf8701856bd 100644 --- a/packages/osd-opensearch/src/cli_commands/snapshot.js +++ b/packages/osd-opensearch/src/cli_commands/snapshot.js @@ -49,10 +49,13 @@ exports.help = (defaults = {}) => { -E Additional key=value settings to pass to OpenSearch --download-only Download the snapshot but don't actually start it --ssl Sets up SSL on OpenSearch + --P OpenSearch plugin artifact URL to install it on the cluster. We can use the flag multiple times + to install multiple plugins on the cluster snapshot. The argument value can be url to zip file, maven coordinates of the plugin + or for local zip files, use file:. Example: - opensearch snapshot --version 5.6.8 -E cluster.name=test -E path.data=/tmp/opensearch-data + opensearch snapshot --version 2.2.0 -E cluster.name=test -E path.data=/tmp/opensearch-data --P org.opensearch.plugin:test-plugin:2.2.0.0 --P file:/home/user/opensearch-test-plugin-2.2.0.0.zip `; }; @@ -64,6 +67,7 @@ exports.run = async (defaults = {}) => { installPath: 'install-path', dataArchive: 'data-archive', opensearchArgs: 'E', + opensearchPlugins: 'P', }, string: ['version'], @@ -83,6 +87,10 @@ exports.run = async (defaults = {}) => { await cluster.extractDataDirectory(installPath, options.dataArchive); } + if (options.opensearchPlugins) { + await cluster.installOpenSearchPlugins(installPath, options.opensearchPlugins); + } + options.bundledJDK = true; await cluster.run(installPath, options); diff --git a/packages/osd-opensearch/src/cluster.js b/packages/osd-opensearch/src/cluster.js index 7b11b4edbbc5..3527668eed05 100644 --- a/packages/osd-opensearch/src/cluster.js +++ b/packages/osd-opensearch/src/cluster.js @@ -34,7 +34,7 @@ const execa = require('execa'); const chalk = require('chalk'); const path = require('path'); const { downloadSnapshot, installSnapshot, installSource, installArchive } = require('./install'); -const { OPENSEARCH_BIN } = require('./paths'); +const { OPENSEARCH_BIN, OPENSEARCH_PLUGIN } = require('./paths'); const { log: defaultLog, parseOpenSearchLog, extractConfigFiles, decompress } = require('./utils'); const { createCliError } = require('./errors'); const { promisify } = require('util'); @@ -170,6 +170,29 @@ exports.Cluster = class Cluster { this._log.indent(-4); } + /** + * Unpacks a tar or zip file containing the OpenSearch plugin directory for an + * OpenSearch cluster. + * + * @param {string} installPath + * @param {Array|string} opensearchPlugins Array or string of OpenSearch plugin(s) artifact url + */ + async installOpenSearchPlugins(installPath, opensearchPluginsPath) { + if (opensearchPluginsPath) { + this._log.info(chalk.bold(`Downloading OpenSearch plugin(s) on the cluster snapshot`)); + this._log.indent(4); + opensearchPluginsPath = + typeof opensearchPluginsPath === 'string' ? [opensearchPluginsPath] : opensearchPluginsPath; + // Run opensearch-plugin tool script to download OpenSearch plugin artifacts + for (const pluginPath of opensearchPluginsPath) { + this._log.info(`Installing OpenSearch Plugin from the path: ${pluginPath}`); + await execa(OPENSEARCH_PLUGIN, [`install`, `--batch`, pluginPath], { cwd: installPath }); + } + this._log.info(`Plugin installation complete`); + this._log.indent(-4); + } + } + /** * Starts OpenSearch and returns resolved promise once started * @@ -229,7 +252,22 @@ exports.Cluster = class Cluster { throw new Error('OpenSearch has not been started'); } - await treeKillAsync(this._process.pid); + /* Temporary fix for https://github.com/opensearch-project/OpenSearch-Dashboards/issues/2811 + * + * `tree-kill` behaves differently on Windows, where it throws if `pid` is already dead, when + * compared to other operating systems, where it silently returns. + */ + try { + await treeKillAsync(this._process.pid); + } catch (ex) { + console.log('ex.message', ex.message); + if ( + process.platform === 'win32' && + !ex.message?.includes(`The process "${this._process.pid}" not found`) + ) { + throw ex; + } + } await this._outcome; } diff --git a/packages/osd-opensearch/src/integration_tests/cluster.test.js b/packages/osd-opensearch/src/integration_tests/cluster.test.js index 43a8f0f63c7b..7b4105aefd49 100644 --- a/packages/osd-opensearch/src/integration_tests/cluster.test.js +++ b/packages/osd-opensearch/src/integration_tests/cluster.test.js @@ -292,6 +292,33 @@ describe('#start(installPath)', () => { }); }); +describe('#installOpenSearchPlugins()', () => { + it('install array of plugins on cluster snapshot', async () => { + const cluster = new Cluster({ log }); + await cluster.installOpenSearchPlugins('foo', ['foo1', 'foo2']); + expect(execa).toHaveBeenCalledTimes(2); + expect(execa).toHaveBeenCalledWith('./bin/opensearch-plugin', ['install', '--batch', 'foo1'], { + cwd: 'foo', + }); + expect(execa).toHaveBeenCalledWith('./bin/opensearch-plugin', ['install', '--batch', 'foo2'], { + cwd: 'foo', + }); + }); + it('installs single plugin on cluster snapshot', async () => { + const cluster = new Cluster({ log }); + await cluster.installOpenSearchPlugins('foo', 'foo1'); + expect(execa).toHaveBeenCalledTimes(1); + expect(execa).toHaveBeenCalledWith('./bin/opensearch-plugin', ['install', '--batch', 'foo1'], { + cwd: 'foo', + }); + }); + it('do not execute plugin installation script when no plugins in the param list', async () => { + const cluster = new Cluster({ log }); + await cluster.installOpenSearchPlugins('foo'); + expect(execa).toHaveBeenCalledTimes(0); + }); +}); + describe('#run()', () => { it('resolves when bin/opensearch exists with 0', async () => { mockOpenSearchBin({ exitCode: 0 }); diff --git a/packages/osd-opensearch/src/paths.js b/packages/osd-opensearch/src/paths.js index e278f20fc5ab..93bb80e97ff1 100644 --- a/packages/osd-opensearch/src/paths.js +++ b/packages/osd-opensearch/src/paths.js @@ -44,3 +44,4 @@ exports.OPENSEARCH_BIN = maybeUseBat('bin/opensearch'); exports.OPENSEARCH_CONFIG = 'config/opensearch.yml'; exports.OPENSEARCH_KEYSTORE_BIN = maybeUseBat('./bin/opensearch-keystore'); +exports.OPENSEARCH_PLUGIN = maybeUseBat('./bin/opensearch-plugin'); diff --git a/packages/osd-optimizer/package.json b/packages/osd-optimizer/package.json index e3ffe9a4930a..98c9e2e34fe0 100644 --- a/packages/osd-optimizer/package.json +++ b/packages/osd-optimizer/package.json @@ -23,7 +23,7 @@ "cpy": "^8.0.0", "core-js": "^3.6.5", "dedent": "^0.7.0", - "del": "^5.1.0", + "del": "^6.1.1", "execa": "^4.0.2", "fibers": "^5.0.3", "jest-diff": "^27.5.1", diff --git a/packages/osd-plugin-helpers/package.json b/packages/osd-plugin-helpers/package.json index 6868d0ae6137..7ee45578a63b 100644 --- a/packages/osd-plugin-helpers/package.json +++ b/packages/osd-plugin-helpers/package.json @@ -19,7 +19,7 @@ "@osd/cross-platform": "1.0.0", "@osd/dev-utils": "1.0.0", "@osd/optimizer": "1.0.0", - "del": "^5.1.0", + "del": "^6.1.1", "execa": "^4.0.2", "gulp-zip": "^5.0.2", "inquirer": "^7.3.3", diff --git a/packages/osd-pm/package.json b/packages/osd-pm/package.json index cda579e14fef..c10af975d939 100644 --- a/packages/osd-pm/package.json +++ b/packages/osd-pm/package.json @@ -42,7 +42,7 @@ "cmd-shim": "^2.1.0", "cpy": "^8.0.0", "dedent": "^0.7.0", - "del": "^5.1.0", + "del": "^6.1.1", "execa": "^4.0.2", "getopts": "^2.2.5", "glob": "^7.1.7", diff --git a/packages/osd-test/package.json b/packages/osd-test/package.json index 4016e4dcc05c..7776afc1de58 100644 --- a/packages/osd-test/package.json +++ b/packages/osd-test/package.json @@ -27,7 +27,7 @@ "dependencies": { "chalk": "^4.1.0", "dedent": "^0.7.0", - "del": "^5.1.0", + "del": "^6.1.1", "exit-hook": "^2.2.0", "getopts": "^2.2.5", "glob": "^7.1.7", diff --git a/packages/osd-test/src/functional_test_runner/integration/failure_hooks.test.js b/packages/osd-test/src/functional_test_runner/integration/failure_hooks.test.js index 2047a6c3065f..9e565671ba5d 100644 --- a/packages/osd-test/src/functional_test_runner/integration/failure_hooks.test.js +++ b/packages/osd-test/src/functional_test_runner/integration/failure_hooks.test.js @@ -55,6 +55,7 @@ describe('failure hooks', function () { { flag: '$FAILING_TEST$', assert(lines) { + expect(lines.shift()).to.match(/\$FAILING_TEST\$/); expect(lines.shift()).to.match(/global before each/); expect(lines.shift()).to.match(/info\s+testFailure\s+\$FAILING_TEST_ERROR\$/); expect(lines.shift()).to.match(/info\s+testFailureAfterDelay\s+\$FAILING_TEST_ERROR\$/); diff --git a/packages/osd-test/src/functional_test_runner/lib/mocha/filter_suites_by_tags.test.js b/packages/osd-test/src/functional_test_runner/lib/mocha/filter_suites_by_tags.test.js index 5781a6fd785f..bc27ff7e1a8a 100644 --- a/packages/osd-test/src/functional_test_runner/lib/mocha/filter_suites_by_tags.test.js +++ b/packages/osd-test/src/functional_test_runner/lib/mocha/filter_suites_by_tags.test.js @@ -111,8 +111,8 @@ it('only runs hooks of parents and tests in level1a', async () => { "suite: ", "suite: level 1", "suite: level 1 level 1a", - "hook: \\"before each\\" hook: rootBeforeEach", - "hook: level 1 \\"before each\\" hook: level1BeforeEach", + "hook: \\"before each\\" hook: rootBeforeEach for \\"test 1a\\"", + "hook: level 1 \\"before each\\" hook: level1BeforeEach for \\"test 1a\\"", "test: level 1 level 1a test 1a", ] `); @@ -130,8 +130,8 @@ it('only runs hooks of parents and tests in level1b', async () => { "suite: ", "suite: level 1", "suite: level 1 level 1b", - "hook: \\"before each\\" hook: rootBeforeEach", - "hook: level 1 \\"before each\\" hook: level1BeforeEach", + "hook: \\"before each\\" hook: rootBeforeEach for \\"test 1b\\"", + "hook: level 1 \\"before each\\" hook: level1BeforeEach for \\"test 1b\\"", "test: level 1 level 1b test 1b", ] `); @@ -149,12 +149,12 @@ it('only runs hooks of parents and tests in level1a and level1b', async () => { "suite: ", "suite: level 1", "suite: level 1 level 1a", - "hook: \\"before each\\" hook: rootBeforeEach", - "hook: level 1 \\"before each\\" hook: level1BeforeEach", + "hook: \\"before each\\" hook: rootBeforeEach for \\"test 1a\\"", + "hook: level 1 \\"before each\\" hook: level1BeforeEach for \\"test 1a\\"", "test: level 1 level 1a test 1a", "suite: level 1 level 1b", - "hook: \\"before each\\" hook: rootBeforeEach", - "hook: level 1 \\"before each\\" hook: level1BeforeEach", + "hook: \\"before each\\" hook: rootBeforeEach for \\"test 1b\\"", + "hook: level 1 \\"before each\\" hook: level1BeforeEach for \\"test 1b\\"", "test: level 1 level 1b test 1b", ] `); @@ -173,8 +173,8 @@ it('only runs level1a if including level1 and excluding level1b', async () => { "suite: ", "suite: level 1", "suite: level 1 level 1a", - "hook: \\"before each\\" hook: rootBeforeEach", - "hook: level 1 \\"before each\\" hook: level1BeforeEach", + "hook: \\"before each\\" hook: rootBeforeEach for \\"test 1a\\"", + "hook: level 1 \\"before each\\" hook: level1BeforeEach for \\"test 1a\\"", "test: level 1 level 1a test 1a", ] `); @@ -193,8 +193,8 @@ it('only runs level1b if including level1 and excluding level1a', async () => { "suite: ", "suite: level 1", "suite: level 1 level 1b", - "hook: \\"before each\\" hook: rootBeforeEach", - "hook: level 1 \\"before each\\" hook: level1BeforeEach", + "hook: \\"before each\\" hook: rootBeforeEach for \\"test 1b\\"", + "hook: level 1 \\"before each\\" hook: level1BeforeEach for \\"test 1b\\"", "test: level 1 level 1b test 1b", ] `); @@ -212,7 +212,7 @@ it('only runs level2 if excluding level1', async () => { "suite: ", "suite: level 2", "suite: level 2 level 2a", - "hook: \\"before each\\" hook: rootBeforeEach", + "hook: \\"before each\\" hook: rootBeforeEach for \\"test 2a\\"", "test: level 2 level 2a test 2a", ] `); diff --git a/packages/osd-ui-shared-deps/package.json b/packages/osd-ui-shared-deps/package.json index b75c4bd2a5c9..14cf480dba72 100644 --- a/packages/osd-ui-shared-deps/package.json +++ b/packages/osd-ui-shared-deps/package.json @@ -42,7 +42,7 @@ "@osd/dev-utils": "1.0.0", "babel-plugin-transform-react-remove-prop-types": "^0.4.24", "css-loader": "^5.2.7", - "del": "^5.1.0", + "del": "^6.1.1", "loader-utils": "^1.2.3", "val-loader": "^2.1.2", "webpack": "^4.41.5" diff --git a/src/core/public/mocks.ts b/src/core/public/mocks.ts index 163e34b4cf1a..e863d627c801 100644 --- a/src/core/public/mocks.ts +++ b/src/core/public/mocks.ts @@ -83,6 +83,7 @@ function createCoreSetupMock({ uiSettings: uiSettingsServiceMock.createSetupContract(), injectedMetadata: { getInjectedVar: injectedMetadataServiceMock.createSetupContract().getInjectedVar, + getBranding: injectedMetadataServiceMock.createSetupContract().getBranding, }, }; diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/bin/opensearch-dashboards-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/bin/opensearch-dashboards-docker index dc05bf8e0910..a5cefbc2397c 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/resources/bin/opensearch-dashboards-docker +++ b/src/dev/build/tasks/os_packages/docker_generator/resources/bin/opensearch-dashboards-docker @@ -149,7 +149,7 @@ opensearch_dashboards_vars=( telemetry.optIn telemetry.optInStatusUrl telemetry.sendUsageFrom - wizard.enabled + vis_builder.enabled data_source.enabled data_source.encryption.wrappingKeyName data_source.encryption.wrappingKeyNamespace diff --git a/src/plugins/charts/README.md b/src/plugins/charts/README.md index f7ff405c74f2..1afd049ea284 100644 --- a/src/plugins/charts/README.md +++ b/src/plugins/charts/README.md @@ -1,29 +1,88 @@ # Charts -The Charts plugin is a way to create easier integration of shared colors, themes, types and other utilities across all OpenSearch Dashboards charts and visualizations. +The Charts plugin provides utility services for accessing shared colors and themes for visual consistency across all OpenSearch Dashboards charts and visualizations. It also provides a number of static utility functions and standard components for user-specified chart configuration. -## Static methods +## Services -### `vislibColorMaps` +### Theme service -Color mappings related to vislib visualizations +A utility service for fetching `chartsTheme` and `chartsBaseTheme`. -### `truncatedColorMaps` +For more, see Theme service [docs](public/services/theme/README.md) -Color mappings subset of `vislibColorMaps` +### Color service +#### Static properties +##### `seedColors` + +A list of colors chosen for visual appeal. + +#### Static methods +##### `mappedColors` -### `colorSchemas` +Get a value-based mapping of colors. + +##### `createColorLookupFunction` + +Factory for color mapping function. + +## Static functions and components +### Color maps +#### `colorSchemas` Color mappings in `value`/`text` form -### `getHeatmapColors` +#### `getHeatmapColors` Function to retrieve heatmap related colors based on `value` and `colorSchemaName` -### `truncatedColorSchemas` +#### `truncatedColorMaps` + +Color mappings subset of `vislibColorMaps` + +#### `truncatedColorSchemas` Truncated color mappings in `value`/`text` form -## Theme +#### `vislibColorMaps` + +Color mappings related to vislib visualizations + +### Components + +Standardized React input UI components which can be used by visualization editors to specify various visualization options. + +#### `BasicOptions` + +Components for specifying legend and tooltip + +#### `ColorRanges` + +Component for specifying color range thresholds + +#### `ColorSchemaOptions` + +Component for specifying color schemas (palettes) + +#### `NumberInputOption` + +Deprecated in favor of `RequiredNumberInputOption` + +#### `RangeOption` + +Component for specifying a numerical value with a slider + +#### `RequiredNumberInputOption` + +Component for specifying numerical values, such as a threshold. + +#### `SelectOption` + +Basic select component + +#### `SwitchOption` + +Basic toggle component + +#### `TextInputOption` -See Theme service [docs](public/services/theme/README.md) +Basic text input component diff --git a/src/plugins/data_source/README.md b/src/plugins/data_source/README.md index a76e9a1fb5a0..ca212f4ded7f 100755 --- a/src/plugins/data_source/README.md +++ b/src/plugins/data_source/README.md @@ -11,15 +11,15 @@ Update the following configuration in the `opensearch_dashboards.yml` file to ap 1. The dataSource plugin is disabled by default; to enable it: `data_source.enabled: true` -2. The audit trail is enabled by default for logging the access to data source; to disable it: - `data_source.audit.enabled: false` +2. The audit trail is disabled by default for logging the access to data source; to disable it: + `data_source.audit.enabled: true` -- Current auditor configuration: +- Default auditor configuration: ```yml data_source.audit.appender.kind: 'file' data_source.audit.appender.layout.kind: 'pattern' -data_source.audit.appender.path: '/tmp/opensearch-dashboards-data-source-audit.log' +data_source.audit.appender.path: '/opensearch-dashboards-data-source-audit.log' ``` 3. The default encryption-related configuration parameters are: diff --git a/src/plugins/data_source/audit_config.ts b/src/plugins/data_source/audit_config.ts index d8c99fbfa846..ec898ff5441e 100644 --- a/src/plugins/data_source/audit_config.ts +++ b/src/plugins/data_source/audit_config.ts @@ -4,6 +4,8 @@ */ import { schema } from '@osd/config-schema'; +import os from 'os'; +import path from 'path'; // eslint-disable-next-line @osd/eslint/no-restricted-paths import { DateConversion } from '../../../src/core/server/logging/layouts/conversions'; @@ -36,7 +38,7 @@ export const fileAppenderSchema = schema.object( kind: 'pattern', highlight: true, }, - path: '/tmp/opensearch-dashboards-data-source-audit.log', + path: path.join(os.tmpdir(), 'opensearch-dashboards-data-source-audit.log'), }, } ); diff --git a/src/plugins/data_source/config.ts b/src/plugins/data_source/config.ts index f2fd79fade9a..1fc4e00c3e23 100644 --- a/src/plugins/data_source/config.ts +++ b/src/plugins/data_source/config.ts @@ -34,7 +34,7 @@ export const configSchema = schema.object({ size: schema.number({ defaultValue: 5 }), }), audit: schema.object({ - enabled: schema.boolean({ defaultValue: true }), + enabled: schema.boolean({ defaultValue: false }), appender: fileAppenderSchema, }), }); diff --git a/src/plugins/data_source_management/public/components/create_data_source_wizard/__snapshots__/create_data_source_wizard.test.tsx.snap b/src/plugins/data_source_management/public/components/create_data_source_wizard/__snapshots__/create_data_source_wizard.test.tsx.snap index b4331e26f79d..c7e3152fca99 100644 --- a/src/plugins/data_source_management/public/components/create_data_source_wizard/__snapshots__/create_data_source_wizard.test.tsx.snap +++ b/src/plugins/data_source_management/public/components/create_data_source_wizard/__snapshots__/create_data_source_wizard.test.tsx.snap @@ -775,6 +775,7 @@ exports[`Datasource Management: Create Datasource Wizard case1: should load reso onChange={[Function]} onFocus={[Function]} placeholder="Password to connect to data source" + spellCheck={false} type="dual" value="" > @@ -811,6 +812,7 @@ exports[`Datasource Management: Create Datasource Wizard case1: should load reso onChange={[Function]} onFocus={[Function]} placeholder="Password to connect to data source" + spellCheck={false} type="password" value="" /> @@ -1749,6 +1751,7 @@ exports[`Datasource Management: Create Datasource Wizard case2: should fail to l onChange={[Function]} onFocus={[Function]} placeholder="Password to connect to data source" + spellCheck={false} type="dual" value="" > @@ -1785,6 +1788,7 @@ exports[`Datasource Management: Create Datasource Wizard case2: should fail to l onChange={[Function]} onFocus={[Function]} placeholder="Password to connect to data source" + spellCheck={false} type="password" value="" /> diff --git a/src/plugins/data_source_management/public/components/create_data_source_wizard/components/create_form/__snapshots__/create_data_source_form.test.tsx.snap b/src/plugins/data_source_management/public/components/create_data_source_wizard/components/create_form/__snapshots__/create_data_source_form.test.tsx.snap index fcd197cc0827..0e8dc0a57a62 100644 --- a/src/plugins/data_source_management/public/components/create_data_source_wizard/components/create_form/__snapshots__/create_data_source_form.test.tsx.snap +++ b/src/plugins/data_source_management/public/components/create_data_source_wizard/components/create_form/__snapshots__/create_data_source_form.test.tsx.snap @@ -1537,6 +1537,7 @@ exports[`Datasource Management: Create Datasource form should create data source onChange={[Function]} onFocus={[Function]} placeholder="Password to connect to data source" + spellCheck={false} type="dual" value="test123" > @@ -1573,6 +1574,7 @@ exports[`Datasource Management: Create Datasource form should create data source onChange={[Function]} onFocus={[Function]} placeholder="Password to connect to data source" + spellCheck={false} type="password" value="test123" /> @@ -2469,6 +2471,7 @@ exports[`Datasource Management: Create Datasource form should render normally 1` onChange={[Function]} onFocus={[Function]} placeholder="Password to connect to data source" + spellCheck={false} type="dual" value="" > @@ -2505,6 +2508,7 @@ exports[`Datasource Management: Create Datasource form should render normally 1` onChange={[Function]} onFocus={[Function]} placeholder="Password to connect to data source" + spellCheck={false} type="password" value="" /> @@ -3418,6 +3422,7 @@ exports[`Datasource Management: Create Datasource form should throw validation e onChange={[Function]} onFocus={[Function]} placeholder="Password to connect to data source" + spellCheck={false} type="dual" value="test123" > @@ -3454,6 +3459,7 @@ exports[`Datasource Management: Create Datasource form should throw validation e onChange={[Function]} onFocus={[Function]} placeholder="Password to connect to data source" + spellCheck={false} type="password" value="test123" /> diff --git a/src/plugins/data_source_management/public/components/create_data_source_wizard/components/create_form/create_data_source_form.tsx b/src/plugins/data_source_management/public/components/create_data_source_wizard/components/create_form/create_data_source_form.tsx index 429790231a5c..b159065822df 100644 --- a/src/plugins/data_source_management/public/components/create_data_source_wizard/components/create_form/create_data_source_form.tsx +++ b/src/plugins/data_source_management/public/components/create_data_source_wizard/components/create_form/create_data_source_form.tsx @@ -276,6 +276,7 @@ export class CreateDataSourceForm extends React.Component< value={this.state.auth.credentials.password || ''} onChange={this.onChangePassword} onBlur={this.validatePassword} + spellCheck={false} data-test-subj="createDataSourceFormPasswordField" /> diff --git a/src/plugins/data_source_management/public/components/edit_data_source/__snapshots__/edit_data_source.test.tsx.snap b/src/plugins/data_source_management/public/components/edit_data_source/__snapshots__/edit_data_source.test.tsx.snap index a3d004457a78..2858167a08c0 100644 --- a/src/plugins/data_source_management/public/components/edit_data_source/__snapshots__/edit_data_source.test.tsx.snap +++ b/src/plugins/data_source_management/public/components/edit_data_source/__snapshots__/edit_data_source.test.tsx.snap @@ -1018,6 +1018,7 @@ exports[`Datasource Management: Edit Datasource Wizard should load resources suc onBlur={[Function]} onChange={[Function]} placeholder="Password to connect to data source" + spellCheck={false} type="dual" value="********" > @@ -1054,6 +1055,7 @@ exports[`Datasource Management: Edit Datasource Wizard should load resources suc onBlur={[Function]} onChange={[Function]} placeholder="Password to connect to data source" + spellCheck={false} type="password" value="********" /> diff --git a/src/plugins/data_source_management/public/components/edit_data_source/components/edit_form/__snapshots__/edit_data_source_form.test.tsx.snap b/src/plugins/data_source_management/public/components/edit_data_source/components/edit_form/__snapshots__/edit_data_source_form.test.tsx.snap index 1ce16bf131c2..84c105d68eef 100644 --- a/src/plugins/data_source_management/public/components/edit_data_source/components/edit_form/__snapshots__/edit_data_source_form.test.tsx.snap +++ b/src/plugins/data_source_management/public/components/edit_data_source/components/edit_form/__snapshots__/edit_data_source_form.test.tsx.snap @@ -965,6 +965,7 @@ exports[`Datasource Management: Edit Datasource Form Case 1: With Username & Pas onBlur={[Function]} onChange={[Function]} placeholder="Password to connect to data source" + spellCheck={false} type="dual" value="********" > @@ -1001,6 +1002,7 @@ exports[`Datasource Management: Edit Datasource Form Case 1: With Username & Pas onBlur={[Function]} onChange={[Function]} placeholder="Password to connect to data source" + spellCheck={false} type="password" value="********" /> diff --git a/src/plugins/data_source_management/public/components/edit_data_source/components/edit_form/edit_data_source_form.tsx b/src/plugins/data_source_management/public/components/edit_data_source/components/edit_form/edit_data_source_form.tsx index bda8709cf509..46c91ad540c8 100644 --- a/src/plugins/data_source_management/public/components/edit_data_source/components/edit_form/edit_data_source_form.tsx +++ b/src/plugins/data_source_management/public/components/edit_data_source/components/edit_form/edit_data_source_form.tsx @@ -585,6 +585,7 @@ export class EditDataSourceForm extends React.Component @@ -205,6 +206,7 @@ exports[`Datasource Management: Update Stored Password Modal should render norma id="generated-id" name="confirmUpdatedPassword" placeholder="Confirm Updated password" + spellcheck="false" type="password" value="" /> @@ -529,6 +531,7 @@ exports[`Datasource Management: Update Stored Password Modal should render norma onChange={[Function]} onFocus={[Function]} placeholder="Updated password" + spellCheck={false} type="dual" value="" > @@ -566,6 +569,7 @@ exports[`Datasource Management: Update Stored Password Modal should render norma onChange={[Function]} onFocus={[Function]} placeholder="Updated password" + spellCheck={false} type="password" value="" /> @@ -691,6 +695,7 @@ exports[`Datasource Management: Update Stored Password Modal should render norma onChange={[Function]} onFocus={[Function]} placeholder="Confirm Updated password" + spellCheck={false} type="dual" value="" > @@ -728,6 +733,7 @@ exports[`Datasource Management: Update Stored Password Modal should render norma onChange={[Function]} onFocus={[Function]} placeholder="Confirm Updated password" + spellCheck={false} type="password" value="" /> diff --git a/src/plugins/data_source_management/public/components/edit_data_source/components/update_password_modal/update_password_modal.tsx b/src/plugins/data_source_management/public/components/edit_data_source/components/update_password_modal/update_password_modal.tsx index 95ca0abe6e90..693a8a84234e 100644 --- a/src/plugins/data_source_management/public/components/edit_data_source/components/update_password_modal/update_password_modal.tsx +++ b/src/plugins/data_source_management/public/components/edit_data_source/components/update_password_modal/update_password_modal.tsx @@ -125,6 +125,7 @@ export const UpdatePasswordModal = ({ type={'dual'} value={newPassword} isInvalid={!isNewPasswordValid} + spellCheck={false} onChange={(e) => setNewPassword(e.target.value)} onBlur={validateNewPassword} /> @@ -149,6 +150,7 @@ export const UpdatePasswordModal = ({ type={'dual'} value={confirmNewPassword} isInvalid={!!isConfirmNewPasswordValid.length} + spellCheck={false} onChange={(e) => setConfirmNewPassword(e.target.value)} onBlur={validateConfirmNewPassword} /> diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field.test.tsx b/src/plugins/discover/public/application/components/sidebar/discover_field.test.tsx index 39c5c0abed0d..1b384a4b5550 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_field.test.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_field.test.tsx @@ -29,6 +29,7 @@ */ import React from 'react'; +// @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; // @ts-ignore import stubbedLogstashFields from 'fixtures/logstash_fields'; @@ -99,8 +100,9 @@ function getComponent({ const props = { indexPattern, + columns: [], field: finalField, - getDetails: jest.fn(() => ({ buckets: [], error: '', exists: 1, total: true, columns: [] })), + getDetails: jest.fn(() => ({ buckets: [], error: '', exists: 1, total: 1 })), onAddFilter: jest.fn(), onAddField: jest.fn(), onRemoveField: jest.fn(), diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field.tsx b/src/plugins/discover/public/application/components/sidebar/discover_field.tsx index 157cb88e782a..e807267435eb 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_field.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_field.tsx @@ -40,6 +40,10 @@ import { getFieldTypeName } from './lib/get_field_type_name'; import './discover_field.scss'; export interface DiscoverFieldProps { + /** + * the selected columns displayed in the doc table in discover + */ + columns: string[]; /** * The displayed field */ @@ -76,6 +80,7 @@ export interface DiscoverFieldProps { } export function DiscoverField({ + columns, field, indexPattern, onAddField, @@ -228,9 +233,10 @@ export function DiscoverField({ {infoIsOpen && ( )} diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field_bucket.tsx b/src/plugins/discover/public/application/components/sidebar/discover_field_bucket.tsx index 1f1af8e91331..6a4dbe295e50 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_field_bucket.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_field_bucket.tsx @@ -68,7 +68,7 @@ export function DiscoverFieldBucket({ field, bucket, onAddFilter }: Props) { title={ bucket.display === '' ? emptyTxt - : `${bucket.display}: ${bucket.count} (${bucket.percent}%)` + : `${bucket.display}: ${bucket.count} (${bucket.percent.toFixed(1)}%)` } size="xs" className="eui-textTruncate" @@ -78,7 +78,7 @@ export function DiscoverFieldBucket({ field, bucket, onAddFilter }: Props) { - {bucket.percent}% + {bucket.percent.toFixed(1)}% diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field_details.test.tsx b/src/plugins/discover/public/application/components/sidebar/discover_field_details.test.tsx index c57300f3032b..63d5c7ace303 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_field_details.test.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_field_details.test.tsx @@ -29,15 +29,26 @@ */ import React from 'react'; +// @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; +import { act } from '@testing-library/react'; // @ts-ignore import stubbedLogstashFields from 'fixtures/logstash_fields'; -import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; import { DiscoverFieldDetails } from './discover_field_details'; import { coreMock } from '../../../../../../core/public/mocks'; import { IndexPatternField } from '../../../../../data/public'; import { getStubIndexPattern } from '../../../../../data/public/test_utils'; +const mockGetHref = jest.fn(); +const mockGetTriggerCompatibleActions = jest.fn(); + +jest.mock('../../../opensearch_dashboards_services', () => ({ + getUiActions: () => ({ + getTriggerCompatibleActions: mockGetTriggerCompatibleActions, + }), +})); + const indexPattern = getStubIndexPattern( 'logstash-*', (cfg: any) => cfg, @@ -48,17 +59,187 @@ const indexPattern = getStubIndexPattern( describe('discover sidebar field details', function () { const defaultProps = { + columns: [], + details: { buckets: [], error: '', exists: 1, total: 1 }, indexPattern, - details: { buckets: [], error: '', exists: 1, total: true, columns: [] }, onAddFilter: jest.fn(), }; - function mountComponent(field: IndexPatternField) { - const compProps = { ...defaultProps, field }; + beforeEach(() => { + mockGetHref.mockReturnValue('/foo/bar'); + mockGetTriggerCompatibleActions.mockReturnValue([ + { + getHref: mockGetHref, + }, + ]); + }); + + function mountComponent(field: IndexPatternField, props?: Record) { + const compProps = { ...defaultProps, ...props, field }; return mountWithIntl(); } - it('should enable the visualize link for a number field', function () { + it('should render buckets if they exist', async function () { + const visualizableField = new IndexPatternField( + { + name: 'bytes', + type: 'number', + esTypes: ['long'], + count: 10, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + 'bytes' + ); + const buckets = [1, 2, 3].map((n) => ({ + display: `display-${n}`, + value: `value-${n}`, + percent: 25, + count: 100, + })); + const comp = mountComponent(visualizableField, { + details: { ...defaultProps.details, buckets }, + }); + expect(findTestSubject(comp, 'fieldVisualizeError').length).toBe(0); + expect(findTestSubject(comp, 'fieldVisualizeBucketContainer').length).toBe(1); + expect(findTestSubject(comp, 'fieldVisualizeBucketContainer').children().length).toBe( + buckets.length + ); + // Visualize link should not be rendered until async hook update + expect(findTestSubject(comp, 'fieldVisualizeLink').length).toBe(0); + expect(findTestSubject(comp, 'fieldVisualize-bytes').length).toBe(0); + + // Complete async hook + await act(async () => { + await nextTick(); + comp.update(); + }); + expect(findTestSubject(comp, 'fieldVisualizeError').length).toBe(0); + expect(findTestSubject(comp, 'fieldVisualizeBucketContainer').length).toBe(1); + expect(findTestSubject(comp, 'fieldVisualizeBucketContainer').children().length).toBe( + buckets.length + ); + expect(findTestSubject(comp, 'fieldVisualizeLink').length).toBe(1); + expect(findTestSubject(comp, 'fieldVisualize-bytes').length).toBe(1); + }); + + it('should only render buckets if they exist', async function () { + const visualizableField = new IndexPatternField( + { + name: 'bytes', + type: 'number', + esTypes: ['long'], + count: 10, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + 'bytes' + ); + const comp = mountComponent(visualizableField); + expect(findTestSubject(comp, 'fieldVisualizeContainer').length).toBe(1); + expect(findTestSubject(comp, 'fieldVisualizeError').length).toBe(0); + expect(findTestSubject(comp, 'fieldVisualizeBucketContainer').length).toBe(0); + expect(findTestSubject(comp, 'fieldVisualizeLink').length).toBe(0); + expect(findTestSubject(comp, 'fieldVisualize-bytes').length).toBe(0); + + await act(async () => { + await nextTick(); + comp.update(); + }); + + expect(findTestSubject(comp, 'fieldVisualizeContainer').length).toBe(1); + expect(findTestSubject(comp, 'fieldVisualizeError').length).toBe(0); + expect(findTestSubject(comp, 'fieldVisualizeBucketContainer').length).toBe(0); + expect(findTestSubject(comp, 'fieldVisualizeLink').length).toBe(1); + expect(findTestSubject(comp, 'fieldVisualize-bytes').length).toBe(1); + }); + + it('should render a details error', async function () { + const visualizableField = new IndexPatternField( + { + name: 'bytes', + type: 'number', + esTypes: ['long'], + count: 10, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + 'bytes' + ); + const errText = 'Some error'; + const comp = mountComponent(visualizableField, { + details: { ...defaultProps.details, error: errText }, + }); + expect(findTestSubject(comp, 'fieldVisualizeContainer').length).toBe(1); + expect(findTestSubject(comp, 'fieldVisualizeBucketContainer').length).toBe(0); + expect(findTestSubject(comp, 'fieldVisualizeError').length).toBe(1); + expect(findTestSubject(comp, 'fieldVisualizeError').text()).toBe(errText); + + await act(async () => { + await nextTick(); + comp.update(); + }); + expect(findTestSubject(comp, 'fieldVisualizeLink').length).toBe(1); + expect(findTestSubject(comp, 'fieldVisualize-bytes').length).toBe(1); + }); + + it('should handle promise rejection from isFieldVisualizable', async function () { + mockGetTriggerCompatibleActions.mockRejectedValue(new Error('Async error')); + const visualizableField = new IndexPatternField( + { + name: 'bytes', + type: 'number', + esTypes: ['long'], + count: 10, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + 'bytes' + ); + const comp = mountComponent(visualizableField); + + await act(async () => { + await nextTick(); + comp.update(); + }); + expect(findTestSubject(comp, 'fieldVisualizeLink').length).toBe(0); + expect(findTestSubject(comp, 'fieldVisualize-bytes').length).toBe(0); + }); + + it('should handle promise rejection from getVisualizeHref', async function () { + mockGetHref.mockRejectedValue(new Error('Async error')); + const visualizableField = new IndexPatternField( + { + name: 'bytes', + type: 'number', + esTypes: ['long'], + count: 10, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + 'bytes' + ); + const comp = mountComponent(visualizableField); + + await act(async () => { + await nextTick(); + comp.update(); + }); + expect(findTestSubject(comp, 'fieldVisualizeLink').length).toBe(0); + expect(findTestSubject(comp, 'fieldVisualize-bytes').length).toBe(0); + }); + + it('should enable the visualize link for a number field', async function () { const visualizableField = new IndexPatternField( { name: 'bytes', @@ -73,10 +254,17 @@ describe('discover sidebar field details', function () { 'bytes' ); const comp = mountComponent(visualizableField); - expect(findTestSubject(comp, 'fieldVisualize-bytes')).toBeTruthy(); + + await act(async () => { + await nextTick(); + comp.update(); + }); + expect(findTestSubject(comp, 'fieldVisualizeLink').length).toBe(1); + expect(findTestSubject(comp, 'fieldVisualize-bytes').length).toBe(1); }); - it('should disable the visualize link for an _id field', function () { + it('should disable the visualize link for an _id field', async function () { + expect.assertions(1); const conflictField = new IndexPatternField( { name: '_id', @@ -91,10 +279,15 @@ describe('discover sidebar field details', function () { 'test' ); const comp = mountComponent(conflictField); - expect(findTestSubject(comp, 'fieldVisualize-_id')).toEqual({}); + + await act(async () => { + await nextTick(); + comp.update(); + }); + expect(findTestSubject(comp, 'fieldVisualize-_id').length).toBe(0); }); - it('should disable the visualize link for an unknown field', function () { + it('should disable the visualize link for an unknown field', async function () { const unknownField = new IndexPatternField( { name: 'test', @@ -109,6 +302,11 @@ describe('discover sidebar field details', function () { 'test' ); const comp = mountComponent(unknownField); - expect(findTestSubject(comp, 'fieldVisualize-test')).toEqual({}); + + await act(async () => { + await nextTick(); + comp.update(); + }); + expect(findTestSubject(comp, 'fieldVisualize-test').length).toBe(0); }); }); diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field_details.tsx b/src/plugins/discover/public/application/components/sidebar/discover_field_details.tsx index 1fbcebbc0c8c..906c173ed07d 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_field_details.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_field_details.tsx @@ -43,16 +43,18 @@ import { IndexPatternField, IndexPattern } from '../../../../../data/public'; import './discover_field_details.scss'; interface DiscoverFieldDetailsProps { + columns: string[]; + details: FieldDetails; field: IndexPatternField; indexPattern: IndexPattern; - details: FieldDetails; onAddFilter: (field: IndexPatternField | string, value: string, type: '+' | '-') => void; } export function DiscoverFieldDetails({ + columns, + details, field, indexPattern, - details, onAddFilter, }: DiscoverFieldDetailsProps) { const warnings = getWarnings(field); @@ -60,37 +62,37 @@ export function DiscoverFieldDetails({ const [visualizeLink, setVisualizeLink] = useState(''); useEffect(() => { - isFieldVisualizable(field, indexPattern.id, details.columns).then( - (flag) => { - setShowVisualizeLink(flag); - // get href only if Visualize button is enabled - getVisualizeHref(field, indexPattern.id, details.columns).then( - (uri) => { - if (uri) setVisualizeLink(uri); - }, - () => { - setVisualizeLink(''); - } - ); - }, - () => { - setShowVisualizeLink(false); + const checkIfVisualizable = async () => { + const visualizable = await isFieldVisualizable(field, indexPattern.id, columns).catch( + () => false + ); + + setShowVisualizeLink(visualizable); + if (visualizable) { + const href = await getVisualizeHref(field, indexPattern.id, columns).catch(() => ''); + setVisualizeLink(href || ''); } - ); - }, [field, indexPattern.id, details.columns]); + }; + checkIfVisualizable(); + }, [field, indexPattern.id, columns]); const handleVisualizeLinkClick = (event: React.MouseEvent) => { // regular link click. let the uiActions code handle the navigation and show popup if needed event.preventDefault(); - triggerVisualizeActions(field, indexPattern.id, details.columns); + triggerVisualizeActions(field, indexPattern.id, columns); }; return ( <> -
- {details.error && {details.error}} - {!details.error && ( -
+
+ {details.error && ( + + {details.error} + + )} + + {!details.error && details.buckets.length > 0 && ( +
{details.buckets.map((bucket: Bucket, idx: number) => ( )} - {showVisualizeLink && ( - <> + {showVisualizeLink && visualizeLink && ( +
{/* eslint-disable-next-line @elastic/eui/href-or-on-click */} 0 && ( )} - +
)}
{!details.error && ( diff --git a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx index f957b93a4cc4..865aff590286 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx @@ -117,8 +117,8 @@ export function DiscoverSidebar({ ); const getDetailsByField = useCallback( - (ipField: IndexPatternField) => getDetails(ipField, hits, columns, selectedIndexPattern), - [hits, columns, selectedIndexPattern] + (ipField: IndexPatternField) => getDetails(ipField, hits, selectedIndexPattern), + [hits, selectedIndexPattern] ); const popularLimit = services.uiSettings.get(FIELDS_LIMIT_SETTING); @@ -199,6 +199,7 @@ export function DiscoverSidebar({ className="dscSidebar__item" > ; - let params: any; - let values: any; + let grouped: boolean; + let values: any[]; beforeEach(function () { values = [ ['foo', 'bar'], @@ -88,30 +76,28 @@ describe('fieldCalculator', function () { 'foo', undefined, ]; - params = {}; - groups = fieldCalculator._groupValues(values, params); + groups = groupValues(values, grouped); }); - it('should have a _groupValues that counts values', function () { + it('should return an object values', function () { expect(groups).toBeInstanceOf(Object); }); it('should throw an error if any value is a plain object', function () { expect(function () { - fieldCalculator._groupValues([{}, true, false], params); + groupValues([{}, true, false], grouped); }).toThrowError(); }); it('should handle values with dots in them', function () { values = ['0', '0.........', '0.......,.....']; - params = {}; - groups = fieldCalculator._groupValues(values, params); + groups = groupValues(values, grouped); expect(groups[values[0]].count).toBe(1); expect(groups[values[1]].count).toBe(1); expect(groups[values[2]].count).toBe(1); }); - it('should have a a key for value in the array when not grouping array terms', function () { + it('should have a key for value in the array when not grouping array terms', function () { expect(_.keys(groups).length).toBe(3); expect(groups.foo).toBeInstanceOf(Object); expect(groups.bar).toBeInstanceOf(Object); @@ -119,7 +105,7 @@ describe('fieldCalculator', function () { }); it('should count array terms independently', function () { - expect(groups['foo,bar']).toBe(undefined); + expect(groups['foo,bar']).toBeUndefined(); expect(groups.foo.count).toBe(5); expect(groups.bar.count).toBe(3); expect(groups.baz.count).toBe(1); @@ -127,11 +113,11 @@ describe('fieldCalculator', function () { describe('grouped array terms', function () { beforeEach(function () { - params.grouped = true; - groups = fieldCalculator._groupValues(values, params); + grouped = true; + groups = groupValues(values, grouped); }); - it('should group array terms when passed params.grouped', function () { + it('should group array terms when grouped is true', function () { expect(_.keys(groups).length).toBe(4); expect(groups['foo,bar']).toBeInstanceOf(Object); }); @@ -155,12 +141,12 @@ describe('fieldCalculator', function () { hits = _.each(_.cloneDeep(realHits), (hit) => indexPattern.flattenHit(hit)); }); - it('Should return an array of values for _source fields', function () { - const extensions = fieldCalculator.getFieldValues( + it('should return an array of values for _source fields', function () { + const extensions = getFieldValues({ hits, - indexPattern.fields.getByName('extension'), - indexPattern - ); + field: indexPattern.fields.getByName('extension') as IndexPatternField, + indexPattern, + }); expect(extensions).toBeInstanceOf(Array); expect( _.filter(extensions, function (v) { @@ -170,12 +156,12 @@ describe('fieldCalculator', function () { expect(_.uniq(_.clone(extensions)).sort()).toEqual(['gif', 'html', 'php', 'png']); }); - it('Should return an array of values for core meta fields', function () { - const types = fieldCalculator.getFieldValues( + it('should return an array of values for core meta fields', function () { + const types = getFieldValues({ hits, - indexPattern.fields.getByName('_type'), - indexPattern - ); + field: indexPattern.fields.getByName('_type') as IndexPatternField, + indexPattern, + }); expect(types).toBeInstanceOf(Array); expect( _.filter(types, function (v) { @@ -187,48 +173,96 @@ describe('fieldCalculator', function () { }); describe('getFieldValueCounts', function () { - let params: { hits: any; field: any; count: number; indexPattern: IndexPattern }; + let params: FieldValueCountsParams; beforeEach(function () { params = { hits: _.cloneDeep(realHits), - field: indexPattern.fields.getByName('extension'), + field: indexPattern.fields.getByName('extension') as IndexPatternField, count: 3, indexPattern, }; }); + it('counts the top 5 values by default', function () { + params.hits = params.hits.map((hit: Record, i) => ({ + ...hit, + _source: { + extension: `${hit._source.extension}-${i}`, + }, + })); + params.count = undefined; + const extensions = getFieldValueCounts(params); + expect(extensions).toBeInstanceOf(Object); + expect(extensions.buckets).toBeInstanceOf(Array); + const buckets = extensions.buckets as Bucket[]; + expect(buckets.length).toBe(5); + expect(extensions.error).toBeUndefined(); + }); + + it('counts only distinct values if less than default', function () { + params.count = undefined; + const extensions = getFieldValueCounts(params); + expect(extensions).toBeInstanceOf(Object); + expect(extensions.buckets).toBeInstanceOf(Array); + const buckets = extensions.buckets as Bucket[]; + expect(buckets.length).toBe(4); + expect(extensions.error).toBeUndefined(); + }); + + it('counts only distinct values if less than specified count', function () { + params.count = 10; + const extensions = getFieldValueCounts(params); + expect(extensions).toBeInstanceOf(Object); + expect(extensions.buckets).toBeInstanceOf(Array); + const buckets = extensions.buckets as Bucket[]; + expect(buckets.length).toBe(4); + expect(extensions.error).toBeUndefined(); + }); + it('counts the top 3 values', function () { - const extensions = fieldCalculator.getFieldValueCounts(params); + const extensions = getFieldValueCounts(params); expect(extensions).toBeInstanceOf(Object); expect(extensions.buckets).toBeInstanceOf(Array); - expect(extensions.buckets.length).toBe(3); - expect(_.map(extensions.buckets, 'value')).toEqual(['html', 'php', 'gif']); - expect(extensions.error).toBe(undefined); + const buckets = extensions.buckets as Bucket[]; + expect(buckets.length).toBe(3); + expect(_.map(buckets, 'value')).toEqual(['html', 'gif', 'php']); + expect(extensions.error).toBeUndefined(); }); it('fails to analyze geo and attachment types', function () { - params.field = indexPattern.fields.getByName('point'); - expect(fieldCalculator.getFieldValueCounts(params).error).not.toBe(undefined); + params.field = indexPattern.fields.getByName('point') as IndexPatternField; + expect(getFieldValueCounts(params).error).not.toBeUndefined(); - params.field = indexPattern.fields.getByName('area'); - expect(fieldCalculator.getFieldValueCounts(params).error).not.toBe(undefined); + params.field = indexPattern.fields.getByName('area') as IndexPatternField; + expect(getFieldValueCounts(params).error).not.toBeUndefined(); - params.field = indexPattern.fields.getByName('request_body'); - expect(fieldCalculator.getFieldValueCounts(params).error).not.toBe(undefined); + params.field = indexPattern.fields.getByName('request_body') as IndexPatternField; + expect(getFieldValueCounts(params).error).not.toBeUndefined(); }); it('fails to analyze fields that are in the mapping, but not the hits', function () { - params.field = indexPattern.fields.getByName('ip'); - expect(fieldCalculator.getFieldValueCounts(params).error).not.toBe(undefined); + params.field = indexPattern.fields.getByName('ip') as IndexPatternField; + expect(getFieldValueCounts(params).error).not.toBeUndefined(); }); it('counts the total hits', function () { - expect(fieldCalculator.getFieldValueCounts(params).total).toBe(params.hits.length); + expect(getFieldValueCounts(params).total).toBe(params.hits.length); }); it('counts the hits the field exists in', function () { - params.field = indexPattern.fields.getByName('phpmemory'); - expect(fieldCalculator.getFieldValueCounts(params).exists).toBe(5); + params.field = indexPattern.fields.getByName('phpmemory') as IndexPatternField; + expect(getFieldValueCounts(params).exists).toBe(5); + }); + + it('catches and returns errors', function () { + params.hits = params.hits.map((hit: Record) => ({ + ...hit, + _source: { + extension: { foo: hit._source.extension }, + }, + })); + params.grouped = true; + expect(typeof getFieldValueCounts(params).error).toBe('string'); }); }); }); diff --git a/src/plugins/discover/public/application/components/sidebar/lib/field_calculator.ts b/src/plugins/discover/public/application/components/sidebar/lib/field_calculator.ts new file mode 100644 index 000000000000..54f8832fa1fc --- /dev/null +++ b/src/plugins/discover/public/application/components/sidebar/lib/field_calculator.ts @@ -0,0 +1,148 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * 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 '@osd/i18n'; +import { IndexPattern, IndexPatternField } from 'src/plugins/data/public'; +import { FieldValueCounts } from '../types'; + +const NO_ANALYSIS_TYPES = ['geo_point', 'geo_shape', 'attachment']; + +interface FieldValuesParams { + hits: Array>; + field: IndexPatternField; + indexPattern: IndexPattern; +} + +interface FieldValueCountsParams extends FieldValuesParams { + count?: number; + grouped?: boolean; +} + +const getFieldValues = ({ hits, field, indexPattern }: FieldValuesParams) => { + const name = field.name; + const flattenHit = indexPattern.flattenHit; + return hits.map((hit) => flattenHit(hit)[name]); +}; + +const getFieldValueCounts = (params: FieldValueCountsParams): FieldValueCounts => { + const { hits, field, indexPattern, count = 5, grouped = false } = params; + const { type: fieldType } = field; + + if (NO_ANALYSIS_TYPES.includes(fieldType)) { + return { + error: i18n.translate( + 'discover.fieldChooser.fieldCalculator.analysisIsNotAvailableForGeoFieldsErrorMessage', + { + defaultMessage: 'Analysis is not available for {fieldType} fields.', + values: { + fieldType, + }, + } + ), + }; + } + + const allValues = getFieldValues({ hits, field, indexPattern }); + const missing = allValues.filter((v) => v === undefined || v === null).length; + + try { + const groups = groupValues(allValues, grouped); + const counts = Object.keys(groups) + .sort((a, b) => groups[b].count - groups[a].count) + .slice(0, count) + .map((key) => ({ + value: groups[key].value, + count: groups[key].count, + percent: (groups[key].count / (hits.length - missing)) * 100, + display: indexPattern.getFormatterForField(field).convert(groups[key].value), + })); + + if (hits.length === missing) { + return { + error: i18n.translate( + 'discover.fieldChooser.fieldCalculator.fieldIsNotPresentInDocumentsErrorMessage', + { + defaultMessage: + 'This field is present in your OpenSearch mapping but not in the {hitsLength} documents shown in the doc table. You may still be able to visualize or search on it.', + values: { + hitsLength: hits.length, + }, + } + ), + }; + } + + return { + total: hits.length, + exists: hits.length - missing, + missing, + buckets: counts, + }; + } catch (e) { + return { + error: e instanceof Error ? e.message : String(e), + }; + } +}; + +const groupValues = ( + allValues: any[], + grouped?: boolean +): Record => { + const values = grouped ? allValues : allValues.flat(); + + return values + .filter((v) => { + if (v instanceof Object && !Array.isArray(v)) { + throw new Error( + i18n.translate( + 'discover.fieldChooser.fieldCalculator.analysisIsNotAvailableForObjectFieldsErrorMessage', + { + defaultMessage: 'Analysis is not available for object fields.', + } + ) + ); + } + return v !== undefined && v !== null; + }) + .reduce((groups, value) => { + if (groups.hasOwnProperty(value)) { + groups[value].count++; + } else { + groups[value] = { + value, + count: 1, + }; + } + return groups; + }, {}); +}; + +export { FieldValueCountsParams, groupValues, getFieldValues, getFieldValueCounts }; diff --git a/src/plugins/discover/public/application/components/sidebar/lib/get_details.ts b/src/plugins/discover/public/application/components/sidebar/lib/get_details.ts index fb8f22e202cd..823cbde9ba72 100644 --- a/src/plugins/discover/public/application/components/sidebar/lib/get_details.ts +++ b/src/plugins/discover/public/application/components/sidebar/lib/get_details.ts @@ -29,27 +29,38 @@ */ // @ts-ignore -import { fieldCalculator } from './field_calculator'; +import { i18n } from '@osd/i18n'; +import { getFieldValueCounts } from './field_calculator'; import { IndexPattern, IndexPatternField } from '../../../../../../data/public'; export function getDetails( field: IndexPatternField, hits: Array>, - columns: string[], indexPattern?: IndexPattern ) { + const defaultDetails = { + error: '', + exists: 0, + total: 0, + buckets: [], + }; if (!indexPattern) { - return {}; + return { + ...defaultDetails, + error: i18n.translate('discover.fieldChooser.noIndexPatternSelectedErrorMessage', { + defaultMessage: 'Index pattern not specified.', + }), + }; } const details = { - ...fieldCalculator.getFieldValueCounts({ + ...defaultDetails, + ...getFieldValueCounts({ hits, field, indexPattern, count: 5, grouped: false, }), - columns, }; if (details.buckets) { for (const bucket of details.buckets) { diff --git a/src/plugins/discover/public/application/components/sidebar/types.ts b/src/plugins/discover/public/application/components/sidebar/types.ts index b254057b0de0..a43120b28e96 100644 --- a/src/plugins/discover/public/application/components/sidebar/types.ts +++ b/src/plugins/discover/public/application/components/sidebar/types.ts @@ -36,9 +36,12 @@ export interface IndexPatternRef { export interface FieldDetails { error: string; exists: number; - total: boolean; + total: number; buckets: Bucket[]; - columns: string[]; +} + +export interface FieldValueCounts extends Partial { + missing?: number; } export interface Bucket { diff --git a/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/__snapshots__/empty_state.test.tsx.snap b/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/__snapshots__/empty_state.test.tsx.snap index 4b18bf314541..a617fa0511e2 100644 --- a/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/__snapshots__/empty_state.test.tsx.snap +++ b/src/plugins/index_pattern_management/public/components/index_pattern_table/empty_state/__snapshots__/empty_state.test.tsx.snap @@ -30,33 +30,6 @@ exports[`EmptyState should render normally 1`] = ` columns={3} responsive={true} > - - - } - icon={ - - } - onClick={[Function]} - title={ - - } - /> - + {/* TODO: [UNCOMMENTME] Once we have long-term fix for https://github.com/opensearch-project/OpenSearch-Dashboards/issues/2684 } /> - + */} {getMlCardState() !== MlCardState.HIDDEN ? mlCard : <>} + {`Custom WMS Configuration`} - + ), }} /> diff --git a/src/plugins/vis_builder/README.md b/src/plugins/vis_builder/README.md index 88b5afbda1f4..4bbf82d9dc87 100755 --- a/src/plugins/vis_builder/README.md +++ b/src/plugins/vis_builder/README.md @@ -31,6 +31,6 @@ Outline: **Notes:** -- Currently only the metric viz is defined, so schema properties that other vis types might need may be missing and require further setup. +- Currently only the metric and table viz are defined, so schema properties that other vis types might need may be missing and require further setup. - `to_expression` has not yet been abstracted into a common utility for different visualizations. Adding more visualization types should make it easier to identify which parts of expression creation are common, and which are visualization-specific. diff --git a/src/plugins/vis_builder/public/application/components/data_tab/dropbox.tsx b/src/plugins/vis_builder/public/application/components/data_tab/dropbox.tsx index f6b7a6ca221b..70b43a2c6014 100644 --- a/src/plugins/vis_builder/public/application/components/data_tab/dropbox.tsx +++ b/src/plugins/vis_builder/public/application/components/data_tab/dropbox.tsx @@ -3,6 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { i18n } from '@osd/i18n'; import { EuiButtonIcon, EuiDragDropContext, @@ -127,7 +128,11 @@ const DropboxComponent = ({ } ${canDrop ? 'canDrop' : ''}`} {...(isValidDropTarget && dropProps)} > - Click or drop to add + + {i18n.translate('visBuilder.dropbox.addField.title', { + defaultMessage: 'Click or drop to add', + })} + { + it('should render normal fields without a dragValue specified', async () => { + const props = { + field: new IndexPatternField( + { + name: 'bytes', + type: 'number', + esTypes: ['long'], + count: 10, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + 'bytes' + ), + }; + render(); + + const button = screen.getByTestId('field-bytes-showDetails'); + + expect(button).toBeDefined(); + }); + + // TODO: it('should allow specified dragValue to override the field name'); + + // TODO: it('should make dots wrappable'); + + // TODO: it('should use a non-scripted FieldIcon by default'); + }); + + // TODO: describe('Field', function () { }); +}); diff --git a/src/plugins/vis_builder/public/application/components/data_tab/field.tsx b/src/plugins/vis_builder/public/application/components/data_tab/field.tsx new file mode 100644 index 000000000000..287c6aed621c --- /dev/null +++ b/src/plugins/vis_builder/public/application/components/data_tab/field.tsx @@ -0,0 +1,113 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * 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, { useState } from 'react'; +import { EuiPopover } from '@elastic/eui'; + +import { IndexPatternField } from '../../../../../data/public'; +import { + FieldButton, + FieldButtonProps, + FieldIcon, +} from '../../../../../opensearch_dashboards_react/public'; + +import { COUNT_FIELD, useDrag } from '../../utils/drag_drop'; +import { FieldDetailsView } from './field_details'; +import { FieldDetails } from './types'; +import './field.scss'; + +export interface FieldProps { + field: IndexPatternField; + getDetails: (field) => FieldDetails; +} + +// TODO: Add field sections (Available fields, popular fields from src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx) +export const Field = ({ field, getDetails }: FieldProps) => { + const [infoIsOpen, setOpen] = useState(false); + + function togglePopover() { + setOpen(!infoIsOpen); + } + + return ( + } + isOpen={infoIsOpen} + closePopover={() => setOpen(false)} + anchorPosition="rightUp" + panelClassName="vbItem__fieldPopoverPanel" + // TODO: make reposition on scroll actually work: https://github.com/opensearch-project/OpenSearch-Dashboards/issues/2782 + repositionOnScroll + data-test-subj="field-popover" + > + {infoIsOpen && } + + ); +}; + +export interface DraggableFieldButtonProps extends Partial { + dragValue?: IndexPatternField['name'] | null | typeof COUNT_FIELD; + field: Partial & Pick; +} + +export const DraggableFieldButton = ({ dragValue, field, ...rest }: DraggableFieldButtonProps) => { + const { name, displayName, type, scripted = false } = field; + const [dragProps] = useDrag({ + namespace: 'field-data', + value: dragValue ?? name, + }); + + function wrapOnDot(str: string) { + // u200B is a non-width white-space character, which allows + // the browser to efficiently word-wrap right after the dot + // without us having to draw a lot of extra DOM elements, etc + return str.replace(/\./g, '.\u200B'); + } + + const defaultIcon = ; + + const defaultFieldName = ( + + {wrapOnDot(displayName)} + + ); + + const defaultProps = { + className: 'vbFieldButton', + dataTestSubj: `field-${name}-showDetails`, + fieldIcon: defaultIcon, + fieldName: defaultFieldName, + onClick: () => {}, + }; + + return ; +}; diff --git a/src/plugins/vis_builder/public/application/components/data_tab/field_bucket.scss b/src/plugins/vis_builder/public/application/components/data_tab/field_bucket.scss new file mode 100644 index 000000000000..50951d850a62 --- /dev/null +++ b/src/plugins/vis_builder/public/application/components/data_tab/field_bucket.scss @@ -0,0 +1,4 @@ +.vbFieldDetails__barContainer { + // Constrains value to the flex item, and allows for truncation when necessary + min-width: 0; +} diff --git a/src/plugins/vis_builder/public/application/components/data_tab/field_bucket.tsx b/src/plugins/vis_builder/public/application/components/data_tab/field_bucket.tsx new file mode 100644 index 000000000000..1a45857a6550 --- /dev/null +++ b/src/plugins/vis_builder/public/application/components/data_tab/field_bucket.tsx @@ -0,0 +1,110 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { + EuiText, + EuiButtonIcon, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiProgress, +} from '@elastic/eui'; +import { i18n } from '@osd/i18n'; + +import { IndexPatternField } from '../../../../../data/public'; + +import { Bucket } from './types'; +import './field_bucket.scss'; +import { useOnAddFilter } from '../../utils/use'; + +interface FieldBucketProps { + bucket: Bucket; + field: IndexPatternField; +} + +export function FieldBucket({ bucket, field }: FieldBucketProps) { + const { count, display, percent, value } = bucket; + const { filterable: isFilterableField, name: fieldName } = field; + + const onAddFilter = useOnAddFilter(); + + const emptyText = i18n.translate('visBuilder.fieldSelector.detailsView.emptyStringText', { + // We need this to communicate to users when a top value is actually an empty string + defaultMessage: 'Empty string', + }); + const addLabel = i18n.translate( + 'visBuilder.fieldSelector.detailsView.filterValueButtonAriaLabel', + { + defaultMessage: 'Filter for {fieldName}: "{value}"', + values: { fieldName, value }, + } + ); + const removeLabel = i18n.translate( + 'visBuilder.fieldSelector.detailsView.filterOutValueButtonAriaLabel', + { + defaultMessage: 'Filter out {fieldName}: "{value}"', + values: { fieldName, value }, + } + ); + + const displayValue = display || emptyText; + + return ( + <> + + + + + + {displayValue} + + + + + {percent.toFixed(1)}% + + + + + + {/* TODO: Should we have any explanation for non-filterable fields? */} + {isFilterableField && ( + +
+ onAddFilter(field, value, '+')} + aria-label={addLabel} + data-test-subj={`plus-${fieldName}-${value}`} + /> + onAddFilter(field, value, '-')} + aria-label={removeLabel} + data-test-subj={`minus-${fieldName}-${value}`} + /> +
+
+ )} +
+ + + ); +} diff --git a/src/plugins/vis_builder/public/application/components/data_tab/field_details.test.tsx b/src/plugins/vis_builder/public/application/components/data_tab/field_details.test.tsx new file mode 100644 index 000000000000..83a148b2f77b --- /dev/null +++ b/src/plugins/vis_builder/public/application/components/data_tab/field_details.test.tsx @@ -0,0 +1,155 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * 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'; +// @ts-ignore +import { findTestSubject } from '@elastic/eui/lib/test'; +// @ts-ignore +import stubbedLogstashFields from 'fixtures/logstash_fields'; +// @ts-ignore +import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; + +import { IndexPatternField } from '../../../../../data/public'; + +import { FieldDetailsView } from './field_details'; + +const mockUseIndexPatterns = jest.fn(() => ({ selected: 'mockIndexPattern' })); +const mockUseOnAddFilter = jest.fn(); +jest.mock('../../utils/use', () => ({ + useIndexPatterns: jest.fn(() => mockUseIndexPatterns), + useOnAddFilter: jest.fn(() => mockUseOnAddFilter), +})); + +describe('visBuilder field details', function () { + const defaultDetails = { buckets: [], error: '', exists: 1, total: 1 }; + function mountComponent(field: IndexPatternField, props?: Record) { + const compProps = { details: defaultDetails, ...props, field }; + return mountWithIntl(); + } + + it('should render buckets if they exist', async function () { + const field = new IndexPatternField( + { + name: 'bytes', + type: 'number', + esTypes: ['long'], + count: 10, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + 'bytes' + ); + const buckets = [1, 2, 3].map((n) => ({ + display: `display-${n}`, + value: `value-${n}`, + percent: 25, + count: 100, + })); + const comp = mountComponent(field, { + details: { ...defaultDetails, buckets }, + }); + expect(findTestSubject(comp, 'fieldDetailsContainer').length).toBe(1); + expect(findTestSubject(comp, 'fieldDetailsError').length).toBe(0); + expect(findTestSubject(comp, 'fieldDetailsBucketsContainer').length).toBe(1); + expect(findTestSubject(comp, 'fieldDetailsBucketsContainer').children().length).toBe( + buckets.length + ); + expect(findTestSubject(comp, 'fieldDetailsExistsLink').length).toBe(1); + }); + + it('should only render buckets if they exist', async function () { + const field = new IndexPatternField( + { + name: 'bytes', + type: 'number', + esTypes: ['long'], + count: 10, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + 'bytes' + ); + const comp = mountComponent(field); + expect(findTestSubject(comp, 'fieldDetailsContainer').length).toBe(1); + expect(findTestSubject(comp, 'fieldDetailsError').length).toBe(0); + expect(findTestSubject(comp, 'fieldDetailsBucketsContainer').length).toBe(1); + expect(findTestSubject(comp, 'fieldDetailsBucketsContainer').children().length).toBe(0); + expect(findTestSubject(comp, 'fieldDetailsExistsLink').length).toBe(1); + }); + + it('should render a details error', async function () { + const field = new IndexPatternField( + { + name: 'bytes', + type: 'number', + esTypes: ['long'], + count: 10, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + 'bytes' + ); + const errText = 'Some error'; + const comp = mountComponent(field, { + details: { ...defaultDetails, error: errText }, + }); + expect(findTestSubject(comp, 'fieldDetailsContainer').length).toBe(1); + expect(findTestSubject(comp, 'fieldDetailsBucketsContainer').children().length).toBe(0); + expect(findTestSubject(comp, 'fieldDetailsError').length).toBe(1); + expect(findTestSubject(comp, 'fieldDetailsError').text()).toBe(errText); + expect(findTestSubject(comp, 'fieldDetailsExistsLink').length).toBe(0); + }); + + it('should not render an exists filter link for scripted fields', async function () { + const field = new IndexPatternField( + { + name: 'bytes', + type: 'number', + esTypes: ['long'], + count: 10, + scripted: true, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + 'bytes' + ); + const comp = mountComponent(field); + expect(findTestSubject(comp, 'fieldDetailsContainer').length).toBe(1); + expect(findTestSubject(comp, 'fieldDetailsError').length).toBe(0); + expect(findTestSubject(comp, 'fieldDetailsExistsLink').length).toBe(0); + }); +}); diff --git a/src/plugins/vis_builder/public/application/components/data_tab/field_details.tsx b/src/plugins/vis_builder/public/application/components/data_tab/field_details.tsx new file mode 100644 index 000000000000..cf6f4974bb18 --- /dev/null +++ b/src/plugins/vis_builder/public/application/components/data_tab/field_details.tsx @@ -0,0 +1,92 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { EuiLink, EuiPopoverFooter, EuiPopoverTitle, EuiText } from '@elastic/eui'; +import { i18n } from '@osd/i18n'; + +import { IndexPatternField } from '../../../../../data/public'; + +import { useIndexPatterns, useOnAddFilter } from '../../utils/use'; +import { FieldBucket } from './field_bucket'; +import { Bucket, FieldDetails } from './types'; + +interface FieldDetailsProps { + field: IndexPatternField; + details: FieldDetails; +} + +export function FieldDetailsView({ field, details }: FieldDetailsProps) { + const { buckets, error, exists, total } = details; + + const onAddFilter = useOnAddFilter(); + const indexPattern = useIndexPatterns().selected; + + const { metaFields = [] } = indexPattern ?? {}; + const isMetaField = metaFields.includes(field.name); + const shouldAllowExistsFilter = !isMetaField && !field.scripted; + + const bucketsTitle = + buckets.length > 1 + ? i18n.translate('visBuilder.fieldSelector.detailsView.fieldTopValuesLabel', { + defaultMessage: 'Top {n} values', + values: { n: buckets.length }, + }) + : i18n.translate('visBuilder.fieldSelector.detailsView.fieldTopValueLabel', { + defaultMessage: 'Top value', + }); + const errorTitle = i18n.translate('visBuilder.fieldSelector.detailsView.fieldNoValuesLabel', { + defaultMessage: 'No values found', + }); + const existsIn = i18n.translate('visBuilder.fieldSelector.detailsView.fieldExistsIn', { + defaultMessage: 'Exists in {exists}', + values: { exists }, + }); + const totalRecords = i18n.translate('visBuilder.fieldSelector.detailsView.fieldTotalRecords', { + defaultMessage: '/ {total} records', + values: { total }, + }); + + const title = buckets.length ? bucketsTitle : errorTitle; + + return ( + <> + {title} +
+ {error ? ( + + {error} + + ) : ( +
+ {buckets.map((bucket: Bucket, idx: number) => ( + + ))} +
+ )} +
+ {!error && ( + + + {shouldAllowExistsFilter ? ( + onAddFilter('_exists_', field.name, '+')} + data-test-subj="fieldDetailsExistsLink" + > + {existsIn} + + ) : ( + <>{exists} + )}{' '} + {totalRecords} + + + )} + + ); +} diff --git a/src/plugins/vis_builder/public/application/components/data_tab/field_selector.test.tsx b/src/plugins/vis_builder/public/application/components/data_tab/field_selector.test.tsx new file mode 100644 index 000000000000..980cfb50c666 --- /dev/null +++ b/src/plugins/vis_builder/public/application/components/data_tab/field_selector.test.tsx @@ -0,0 +1,82 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { fireEvent, render, screen } from '@testing-library/react'; +import { FilterManager, IndexPatternField } from '../../../../../data/public'; +import { FieldGroup } from './field_selector'; + +const mockUseIndexPatterns = jest.fn(() => ({ selected: 'mockIndexPattern' })); +const mockUseOnAddFilter = jest.fn(); +jest.mock('../../utils/use', () => ({ + useIndexPatterns: jest.fn(() => mockUseIndexPatterns), + useOnAddFilter: jest.fn(() => mockUseOnAddFilter), +})); + +const mockGetDetailsByField = jest.fn(() => ({ + buckets: [1, 2, 3].map((n) => ({ + display: `display-${n}`, + value: `value-${n}`, + percent: 25, + count: 100, + })), + error: '', + exists: 100, + total: 150, +})); + +const getFields = (name) => { + return new IndexPatternField( + { + name, + type: 'number', + esTypes: ['long'], + count: 10, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + name + ); +}; + +describe('visBuilder sidebar field selector', function () { + const defaultProps = { + filterManager: {} as FilterManager, + getDetailsByField: mockGetDetailsByField, + header: 'mockHeader', + id: 'mockID', + }; + describe('FieldGroup', () => { + it('renders an empty accordion if no fields specified', async () => { + const { container } = render(); + + expect(container).toHaveTextContent(defaultProps.header); + expect(container).toHaveTextContent('0'); + expect(screen.queryAllByTestId('field-popover').length).toBeFalsy(); + + await fireEvent.click(screen.getByText(defaultProps.header)); + + expect(mockGetDetailsByField).not.toHaveBeenCalled(); + }); + + it('renders an accordion with Fields if fields provided', async () => { + const props = { + ...defaultProps, + fields: ['bytes', 'machine.ram', 'memory', 'phpmemory'].map(getFields), + }; + const { container } = render(); + + expect(container).toHaveTextContent(props.header); + expect(container).toHaveTextContent(props.fields.length.toString()); + expect(screen.queryAllByTestId('field-popover').length).toBe(props.fields.length); + + await fireEvent.click(screen.getByText('memory')); + + expect(mockGetDetailsByField).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/src/plugins/vis_builder/public/application/components/data_tab/field_selector.tsx b/src/plugins/vis_builder/public/application/components/data_tab/field_selector.tsx index 6d3831363c1b..5c82419d5531 100644 --- a/src/plugins/vis_builder/public/application/components/data_tab/field_selector.tsx +++ b/src/plugins/vis_builder/public/application/components/data_tab/field_selector.tsx @@ -3,21 +3,19 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { useState, useEffect, useMemo } from 'react'; +import React, { useState, useEffect, useCallback, useMemo } from 'react'; import { EuiFlexItem, EuiAccordion, EuiNotificationBadge, EuiTitle } from '@elastic/eui'; -import { FieldSearch } from './field_search'; -import { - IndexPatternField, - OPENSEARCH_FIELD_TYPES, - OSD_FIELD_TYPES, -} from '../../../../../data/public'; -import { FieldSelectorField } from './field_selector_field'; +import { IndexPattern, IndexPatternField, OSD_FIELD_TYPES } from '../../../../../data/public'; -import './field_selector.scss'; +import { COUNT_FIELD } from '../../utils/drag_drop'; import { useTypedSelector } from '../../utils/state_management'; -import { useIndexPatterns } from '../../utils/use'; -import { getAvailableFields } from './utils'; +import { useIndexPatterns, useSampleHits } from '../../utils/use'; +import { FieldSearch } from './field_search'; +import { Field, DraggableFieldButton } from './field'; +import { FieldDetails } from './types'; +import { getAvailableFields, getDetails } from './utils'; +import './field_selector.scss'; interface IFieldCategories { categorical: IndexPatternField[]; @@ -25,22 +23,18 @@ interface IFieldCategories { meta: IndexPatternField[]; } -const META_FIELDS: string[] = [ - OPENSEARCH_FIELD_TYPES._ID, - OPENSEARCH_FIELD_TYPES._INDEX, - OPENSEARCH_FIELD_TYPES._SOURCE, - OPENSEARCH_FIELD_TYPES._TYPE, -]; - export const FieldSelector = () => { const indexPattern = useIndexPatterns().selected; const fieldSearchValue = useTypedSelector((state) => state.visualization.searchField); + // TODO: instead of a single fetch of sampled hits for all fields, we should just use the agg service to get top hits or terms per field: https://github.com/opensearch-project/OpenSearch-Dashboards/issues/2780 + const hits = useSampleHits(); const [filteredFields, setFilteredFields] = useState([]); useEffect(() => { - const indexFields = indexPattern?.fields ?? []; + const indexFields = indexPattern?.fields.getAll() ?? []; const filteredSubset = getAvailableFields(indexFields).filter((field) => - field.displayName.includes(fieldSearchValue) + // case-insensitive field search + field.displayName.toLowerCase().includes(fieldSearchValue.toLowerCase()) ); setFilteredFields(filteredSubset); @@ -51,7 +45,7 @@ export const FieldSelector = () => { () => filteredFields?.reduce( (fieldGroups, currentField) => { - const category = getFieldCategory(currentField); + const category = getFieldCategory(currentField, indexPattern); fieldGroups[category].push(currentField); return fieldGroups; @@ -62,7 +56,14 @@ export const FieldSelector = () => { meta: [], } ), - [filteredFields] + [filteredFields, indexPattern] + ); + + const getDetailsByField = useCallback( + (ipField: IndexPatternField) => { + return getDetails(ipField, hits, indexPattern); + }, + [hits, indexPattern] ); return ( @@ -74,20 +75,30 @@ export const FieldSelector = () => {
{/* Count Field */} - + + - -
); @@ -95,37 +106,44 @@ export const FieldSelector = () => { interface FieldGroupProps { fields?: IndexPatternField[]; + getDetailsByField: (ipField: IndexPatternField) => FieldDetails; header: string; id: string; } -const FieldGroup = ({ fields, header, id }: FieldGroupProps) => ( - - {header} - - } - extraAction={ - - {fields?.length || 0} - - } - initialIsOpen - > - {fields?.map((field, i) => ( - - - - ))} - -); +export const FieldGroup = ({ fields, header, id, getDetailsByField }: FieldGroupProps) => { + return ( + + {header} + + } + extraAction={ + + {fields?.length || 0} + + } + initialIsOpen + > + {fields?.map((field, i) => ( + + + + ))} + + ); +}; -function getFieldCategory(field: IndexPatternField): keyof IFieldCategories { - if (META_FIELDS.includes(field.name)) return 'meta'; - if (field.type === OSD_FIELD_TYPES.NUMBER) return 'numerical'; +export const getFieldCategory = ( + { name, type }: IndexPatternField, + indexPattern: IndexPattern | undefined +): keyof IFieldCategories => { + const { metaFields = [] } = indexPattern ?? {}; + if (metaFields.includes(name)) return 'meta'; + if (type === OSD_FIELD_TYPES.NUMBER) return 'numerical'; return 'categorical'; -} +}; diff --git a/src/plugins/vis_builder/public/application/components/data_tab/field_selector_field.tsx b/src/plugins/vis_builder/public/application/components/data_tab/field_selector_field.tsx deleted file mode 100644 index a87e2d184eed..000000000000 --- a/src/plugins/vis_builder/public/application/components/data_tab/field_selector_field.tsx +++ /dev/null @@ -1,86 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Any modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -/* - * 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, { useState } from 'react'; -import { IndexPatternField } from '../../../../../data/public'; -import { FieldButton, FieldIcon } from '../../../../../opensearch_dashboards_react/public'; -import { useDrag } from '../../utils/drag_drop/drag_drop_context'; -import { COUNT_FIELD } from '../../utils/drag_drop/types'; - -import './field_selector_field.scss'; - -export interface FieldSelectorFieldProps { - field: Partial & Pick; -} - -// TODO: -// 1. Add field sections (Available fields, popular fields from src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx) -// 2. Add popover for fields stats from discover as well -export const FieldSelectorField = ({ field }: FieldSelectorFieldProps) => { - const [infoIsOpen, setOpen] = useState(false); - const [dragProps] = useDrag({ - namespace: 'field-data', - value: field.name || COUNT_FIELD, - }); - - function togglePopover() { - setOpen(!infoIsOpen); - } - - function wrapOnDot(str?: string) { - // u200B is a non-width white-space character, which allows - // the browser to efficiently word-wrap right after the dot - // without us having to draw a lot of extra DOM elements, etc - return str ? str.replace(/\./g, '.\u200B') : ''; - } - - const fieldName = ( - - {wrapOnDot(field.displayName)} - - ); - - return ( - } - // fieldAction={actionButton} - fieldName={fieldName} - {...dragProps} - /> - ); -}; diff --git a/src/plugins/vis_builder/public/application/components/data_tab/types.ts b/src/plugins/vis_builder/public/application/components/data_tab/types.ts new file mode 100644 index 000000000000..c7e0327070e7 --- /dev/null +++ b/src/plugins/vis_builder/public/application/components/data_tab/types.ts @@ -0,0 +1,22 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export interface FieldDetails { + buckets: Bucket[]; + error: string; + exists: number; + total: number; +} + +export interface FieldValueCounts extends Partial { + missing?: number; +} + +export interface Bucket { + count: number; + display: string; + percent: number; + value: string; +} diff --git a/src/plugins/vis_builder/public/application/components/data_tab/utils/field_calculator.test.ts b/src/plugins/vis_builder/public/application/components/data_tab/utils/field_calculator.test.ts new file mode 100644 index 000000000000..4f1dfd98fc3b --- /dev/null +++ b/src/plugins/vis_builder/public/application/components/data_tab/utils/field_calculator.test.ts @@ -0,0 +1,268 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * 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'; +// @ts-ignore +import realHits from 'fixtures/real_hits.js'; +// @ts-ignore +import stubbedLogstashFields from 'fixtures/logstash_fields'; +import { coreMock } from '../../../../../../../core/public/mocks'; +import { IndexPattern, IndexPatternField } from '../../../../../../data/public'; +import { getStubIndexPattern } from '../../../../../../data/public/test_utils'; +import { Bucket } from '../types'; +import { + groupValues, + getFieldValues, + getFieldValueCounts, + FieldValueCountsParams, +} from './field_calculator'; + +let indexPattern: IndexPattern; + +describe('field_calculator', function () { + beforeEach(function () { + indexPattern = getStubIndexPattern( + 'logstash-*', + (cfg: any) => cfg, + 'time', + stubbedLogstashFields(), + coreMock.createSetup() + ); + }); + + describe('groupValues', function () { + let groups: Record; + let grouped: boolean; + let values: any[]; + beforeEach(function () { + values = [ + ['foo', 'bar'], + 'foo', + 'foo', + undefined, + ['foo', 'bar'], + 'bar', + 'baz', + null, + null, + null, + 'foo', + undefined, + ]; + groups = groupValues(values, grouped); + }); + + it('should return an object', function () { + expect(groups).toBeInstanceOf(Object); + }); + + it('should throw an error if any value is a plain object', function () { + expect(function () { + groupValues([{}, true, false], grouped); + }).toThrowError(); + }); + + it('should handle values with dots in them', function () { + values = ['0', '0.........', '0.......,.....']; + groups = groupValues(values, grouped); + expect(groups[values[0]].count).toBe(1); + expect(groups[values[1]].count).toBe(1); + expect(groups[values[2]].count).toBe(1); + }); + + it('should have a key for value in the array when not grouping array terms', function () { + expect(_.keys(groups).length).toBe(3); + expect(groups.foo).toBeInstanceOf(Object); + expect(groups.bar).toBeInstanceOf(Object); + expect(groups.baz).toBeInstanceOf(Object); + }); + + it('should count array terms independently', function () { + expect(groups['foo,bar']).toBeUndefined(); + expect(groups.foo.count).toBe(5); + expect(groups.bar.count).toBe(3); + expect(groups.baz.count).toBe(1); + }); + + describe('grouped array terms', function () { + beforeEach(function () { + grouped = true; + groups = groupValues(values, grouped); + }); + + it('should group array terms when grouped is true', function () { + expect(_.keys(groups).length).toBe(4); + expect(groups['foo,bar']).toBeInstanceOf(Object); + }); + + it('should contain the original array as the value', function () { + expect(groups['foo,bar'].value).toEqual(['foo', 'bar']); + }); + + it('should count the pairs separately from the values they contain', function () { + expect(groups['foo,bar'].count).toBe(2); + expect(groups.foo.count).toBe(3); + expect(groups.bar.count).toBe(1); + }); + }); + }); + + describe('getFieldValues', function () { + let hits: any; + + beforeEach(function () { + hits = _.each(_.cloneDeep(realHits), (hit) => indexPattern.flattenHit(hit)); + }); + + it('should return an array of values for _source fields', function () { + const extensions = getFieldValues({ + hits, + field: indexPattern.fields.getByName('extension') as IndexPatternField, + indexPattern, + }); + expect(extensions).toBeInstanceOf(Array); + expect( + _.filter(extensions, function (v) { + return v === 'html'; + }).length + ).toBe(8); + expect(_.uniq(_.clone(extensions)).sort()).toEqual(['gif', 'html', 'php', 'png']); + }); + + it('should return an array of values for core meta fields', function () { + const types = getFieldValues({ + hits, + field: indexPattern.fields.getByName('_type') as IndexPatternField, + indexPattern, + }); + expect(types).toBeInstanceOf(Array); + expect( + _.filter(types, function (v) { + return v === 'apache'; + }).length + ).toBe(18); + expect(_.uniq(_.clone(types)).sort()).toEqual(['apache', 'nginx']); + }); + }); + + describe('getFieldValueCounts', function () { + let params: FieldValueCountsParams; + beforeEach(function () { + params = { + hits: _.cloneDeep(realHits), + field: indexPattern.fields.getByName('extension') as IndexPatternField, + count: 3, + indexPattern, + }; + }); + + it('counts the top 5 values by default', function () { + params.hits = params.hits.map((hit: Record, i) => ({ + ...hit, + _source: { + extension: `${hit._source.extension}-${i}`, + }, + })); + params.count = undefined; + const extensions = getFieldValueCounts(params); + expect(extensions).toBeInstanceOf(Object); + expect(extensions.buckets).toBeInstanceOf(Array); + const buckets = extensions.buckets as Bucket[]; + expect(buckets.length).toBe(5); + expect(extensions.error).toBeUndefined(); + }); + + it('counts only distinct values if less than default', function () { + params.count = undefined; + const extensions = getFieldValueCounts(params); + expect(extensions).toBeInstanceOf(Object); + expect(extensions.buckets).toBeInstanceOf(Array); + const buckets = extensions.buckets as Bucket[]; + expect(buckets.length).toBe(4); + expect(extensions.error).toBeUndefined(); + }); + + it('counts only distinct values if less than specified count', function () { + params.count = 10; + const extensions = getFieldValueCounts(params); + expect(extensions).toBeInstanceOf(Object); + expect(extensions.buckets).toBeInstanceOf(Array); + const buckets = extensions.buckets as Bucket[]; + expect(buckets.length).toBe(4); + expect(extensions.error).toBeUndefined(); + }); + + it('counts the top 3 values', function () { + const extensions = getFieldValueCounts(params); + expect(extensions).toBeInstanceOf(Object); + expect(extensions.buckets).toBeInstanceOf(Array); + const buckets = extensions.buckets as Bucket[]; + expect(buckets.length).toBe(3); + expect(_.map(buckets, 'value')).toEqual(['html', 'gif', 'php']); + expect(extensions.error).toBeUndefined(); + }); + + it('fails to analyze geo and attachment types', function () { + params.field = indexPattern.fields.getByName('point') as IndexPatternField; + expect(getFieldValueCounts(params).error).not.toBeUndefined(); + + params.field = indexPattern.fields.getByName('area') as IndexPatternField; + expect(getFieldValueCounts(params).error).not.toBeUndefined(); + + params.field = indexPattern.fields.getByName('request_body') as IndexPatternField; + expect(getFieldValueCounts(params).error).not.toBeUndefined(); + }); + + it('fails to analyze fields that are in the mapping, but not the hits', function () { + params.field = indexPattern.fields.getByName('ip') as IndexPatternField; + expect(getFieldValueCounts(params).error).not.toBeUndefined(); + }); + + it('counts the total hits', function () { + expect(getFieldValueCounts(params).total).toBe(params.hits.length); + }); + + it('counts the hits the field exists in', function () { + params.field = indexPattern.fields.getByName('phpmemory') as IndexPatternField; + expect(getFieldValueCounts(params).exists).toBe(5); + }); + + it('catches and returns errors', function () { + params.hits = params.hits.map((hit: Record) => ({ + ...hit, + _source: { + extension: { foo: hit._source.extension }, + }, + })); + params.grouped = true; + expect(typeof getFieldValueCounts(params).error).toBe('string'); + }); + }); +}); diff --git a/src/plugins/vis_builder/public/application/components/data_tab/utils/field_calculator.ts b/src/plugins/vis_builder/public/application/components/data_tab/utils/field_calculator.ts new file mode 100644 index 000000000000..bd3cde945d95 --- /dev/null +++ b/src/plugins/vis_builder/public/application/components/data_tab/utils/field_calculator.ts @@ -0,0 +1,124 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { i18n } from '@osd/i18n'; +import { IndexPattern, IndexPatternField } from '../../../../../../data/public'; +import { FieldValueCounts } from '../types'; + +const NO_ANALYSIS_TYPES = ['geo_point', 'geo_shape', 'attachment']; + +interface FieldValuesParams { + hits: Array>; + field: IndexPatternField; + indexPattern: IndexPattern; +} + +interface FieldValueCountsParams extends FieldValuesParams { + count?: number; + grouped?: boolean; +} + +const getFieldValues = ({ hits, field, indexPattern }: FieldValuesParams) => { + // For multi-value fields, we want to flatten based on the parent name instead + const name = field.subType?.multi?.parent ?? field.name; + const flattenHit = indexPattern.flattenHit; + return hits.map((hit) => flattenHit(hit)[name]); +}; + +const getFieldValueCounts = (params: FieldValueCountsParams): FieldValueCounts => { + const { hits, field, indexPattern, count = 5, grouped = false } = params; + const { type: fieldType } = field; + + if (NO_ANALYSIS_TYPES.includes(fieldType)) { + return { + error: i18n.translate( + 'visBuilder.fieldChooser.fieldCalculator.analysisIsNotAvailableForGeoFieldsErrorMessage', + { + defaultMessage: 'Analysis is not available for {fieldType} fields.', + values: { + fieldType, + }, + } + ), + }; + } + + const allValues = getFieldValues({ hits, field, indexPattern }); + const missing = allValues.filter((v) => v === undefined || v === null).length; + + try { + const groups = groupValues(allValues, grouped); + const counts = Object.keys(groups) + .sort((a, b) => groups[b].count - groups[a].count) + .slice(0, count) + .map((key) => ({ + value: groups[key].value, + count: groups[key].count, + percent: (groups[key].count / (hits.length - missing)) * 100, + display: indexPattern.getFormatterForField(field).convert(groups[key].value), + })); + + if (hits.length === missing) { + return { + error: i18n.translate( + 'visBuilder.fieldChooser.fieldCalculator.fieldIsNotPresentInDocumentsErrorMessage', + { + defaultMessage: + 'This field is present in your OpenSearch mapping but not in the {hitsLength} documents sampled. You may still be able to visualize it.', + values: { + hitsLength: hits.length, + }, + } + ), + }; + } + + return { + total: hits.length, + exists: hits.length - missing, + missing, + buckets: counts, + }; + } catch (e) { + return { + error: e instanceof Error ? e.message : String(e), + }; + } +}; + +const groupValues = ( + allValues: any[], + grouped?: boolean +): Record => { + const values = grouped ? allValues : allValues.flat(); + + return values + .filter((v) => { + if (v instanceof Object && !Array.isArray(v)) { + throw new Error( + i18n.translate( + 'visBuilder.fieldChooser.fieldCalculator.analysisIsNotAvailableForObjectFieldsErrorMessage', + { + defaultMessage: 'Analysis is not available for object fields.', + } + ) + ); + } + return v !== undefined && v !== null; + }) + .reduce((groups, value) => { + if (groups.hasOwnProperty(value)) { + groups[value].count++; + } else { + groups[value] = { + value, + count: 1, + }; + } + return groups; + }, {}); +}; + +export { FieldValueCountsParams, groupValues, getFieldValues, getFieldValueCounts }; diff --git a/src/plugins/vis_builder/public/application/components/data_tab/utils/get_field_details.ts b/src/plugins/vis_builder/public/application/components/data_tab/utils/get_field_details.ts new file mode 100644 index 000000000000..75b8b60c0c67 --- /dev/null +++ b/src/plugins/vis_builder/public/application/components/data_tab/utils/get_field_details.ts @@ -0,0 +1,57 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { i18n } from '@osd/i18n'; + +import { IndexPattern, IndexPatternField } from '../../../../../../data/public'; +import { FieldDetails } from '../types'; + +import { getFieldValueCounts } from './field_calculator'; + +export function getFieldDetails( + field: IndexPatternField, + hits: Array>, + indexPattern?: IndexPattern +): FieldDetails { + const defaultDetails = { + error: '', + exists: 0, + total: 0, + buckets: [], + }; + if (!indexPattern) { + return { + ...defaultDetails, + error: i18n.translate('visBuilder.fieldSelector.noIndexPatternSelectedErrorMessage', { + defaultMessage: 'Index pattern not specified.', + }), + }; + } + if (!hits.length) { + return { + ...defaultDetails, + error: i18n.translate('visBuilder.fieldSelector.noHits', { + defaultMessage: + 'No documents match the selected query and filters. Try increasing time range or removing filters.', + }), + }; + } + const details = { + ...defaultDetails, + ...getFieldValueCounts({ + hits, + field, + indexPattern, + count: 5, + grouped: false, + }), + }; + if (details.buckets) { + for (const bucket of details.buckets) { + bucket.display = indexPattern.getFormatterForField(field).convert(bucket.value); + } + } + return details; +} diff --git a/src/plugins/vis_builder/public/application/components/data_tab/utils/index.ts b/src/plugins/vis_builder/public/application/components/data_tab/utils/index.ts index dd0cdea3e23e..2900a66d8f1e 100644 --- a/src/plugins/vis_builder/public/application/components/data_tab/utils/index.ts +++ b/src/plugins/vis_builder/public/application/components/data_tab/utils/index.ts @@ -4,3 +4,4 @@ */ export { getAvailableFields } from './get_available_fields'; +export { getFieldDetails as getDetails } from './get_field_details'; diff --git a/src/plugins/vis_builder/public/application/components/workspace.scss b/src/plugins/vis_builder/public/application/components/workspace.scss index c06e60607665..1d47dccd0e21 100644 --- a/src/plugins/vis_builder/public/application/components/workspace.scss +++ b/src/plugins/vis_builder/public/application/components/workspace.scss @@ -30,6 +30,7 @@ $keyframe-multiplier: 1 / $animation-multiplier; animation: vbDragAnimation #{$total-duartion}s ease-in-out infinite forwards; position: absolute; top: 34.5%; + width: 50% !important; } } diff --git a/src/plugins/vis_builder/public/application/components/workspace.tsx b/src/plugins/vis_builder/public/application/components/workspace.tsx index 23c9f081fdea..3742cafb976a 100644 --- a/src/plugins/vis_builder/public/application/components/workspace.tsx +++ b/src/plugins/vis_builder/public/application/components/workspace.tsx @@ -3,6 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { i18n } from '@osd/i18n'; import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, EuiIcon, EuiPanel } from '@elastic/eui'; import React, { FC, useState, useMemo, useEffect, useLayoutEffect } from 'react'; import { useOpenSearchDashboards } from '../../../../opensearch_dashboards_react/public'; @@ -89,18 +90,29 @@ export const Workspace: FC = ({ children }) => { ) : ( Add a field to start} + title={ +

+ {i18n.translate('visBuilder.workSpace.empty.title', { + defaultMessage: 'Add a field to start', + })} +

+ } body={ <> -

Drag a field to the configuration panel to generate a visualization.

- +

+ {i18n.translate('visBuilder.workSpace.empty.description', { + defaultMessage: + 'Drag a field to the configuration panel to generate a visualization.', + })} +

+
- +
} /> diff --git a/src/plugins/vis_builder/public/application/utils/drag_drop/drag_drop_context.tsx b/src/plugins/vis_builder/public/application/utils/drag_drop/drag_drop_context.tsx index c0f8725a501a..b5c809c9b359 100644 --- a/src/plugins/vis_builder/public/application/utils/drag_drop/drag_drop_context.tsx +++ b/src/plugins/vis_builder/public/application/utils/drag_drop/drag_drop_context.tsx @@ -14,7 +14,7 @@ import React, { } from 'react'; import { DragDataType } from './types'; -// TODO: Replace any with corret type +// TODO: Replace any with correct type // TODO: Split into separate files interface IDragDropContext { data: DragDataType; diff --git a/src/plugins/vis_builder/public/application/utils/drag_drop/index.ts b/src/plugins/vis_builder/public/application/utils/drag_drop/index.ts index 3799a2eb6052..4516a90575ab 100644 --- a/src/plugins/vis_builder/public/application/utils/drag_drop/index.ts +++ b/src/plugins/vis_builder/public/application/utils/drag_drop/index.ts @@ -4,3 +4,4 @@ */ export * from './drag_drop_context'; +export * from './types'; diff --git a/src/plugins/vis_builder/public/application/utils/use/index.ts b/src/plugins/vis_builder/public/application/utils/use/index.ts index 3ba3ca359072..1cc0b28dc89a 100644 --- a/src/plugins/vis_builder/public/application/utils/use/index.ts +++ b/src/plugins/vis_builder/public/application/utils/use/index.ts @@ -4,6 +4,8 @@ */ export { useAggs } from './use_aggs'; -export { useVisualizationType } from './use_visualization_type'; export { useIndexPatterns } from './use_index_pattern'; +export { useOnAddFilter } from './use_on_add_filter'; +export { useSampleHits } from './use_sample_hits'; export { useSavedVisBuilderVis } from './use_saved_vis_builder_vis'; +export { useVisualizationType } from './use_visualization_type'; diff --git a/src/plugins/vis_builder/public/application/utils/use/use_on_add_filter.ts b/src/plugins/vis_builder/public/application/utils/use/use_on_add_filter.ts new file mode 100644 index 000000000000..791521fccad5 --- /dev/null +++ b/src/plugins/vis_builder/public/application/utils/use/use_on_add_filter.ts @@ -0,0 +1,35 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { useCallback } from 'react'; +import { IndexPatternField, opensearchFilters } from '../../../../../data/public'; +import { useOpenSearchDashboards } from '../../../../../opensearch_dashboards_react/public'; +import { VisBuilderServices } from '../../../types'; +import { useIndexPatterns } from './use_index_pattern'; + +export const useOnAddFilter = () => { + const { + services: { + data: { + query: { filterManager }, + }, + }, + } = useOpenSearchDashboards(); + const indexPattern = useIndexPatterns().selected; + const { id = '' } = indexPattern ?? {}; + return useCallback( + (fieldToFilter: IndexPatternField | string, value: string, operation: '+' | '-') => { + const newFilters = opensearchFilters.generateFilters( + filterManager, + fieldToFilter, + value, + operation, + id + ); + return filterManager.addFilters(newFilters); + }, + [filterManager, id] + ); +}; diff --git a/src/plugins/vis_builder/public/application/utils/use/use_sample_hits.ts b/src/plugins/vis_builder/public/application/utils/use/use_sample_hits.ts new file mode 100644 index 000000000000..f3ed75a4dd6a --- /dev/null +++ b/src/plugins/vis_builder/public/application/utils/use/use_sample_hits.ts @@ -0,0 +1,80 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { useEffect, useLayoutEffect, useState } from 'react'; +import { SortDirection } from '../../../../../data/public'; +import { IExpressionLoaderParams } from '../../../../../expressions/public'; +import { useOpenSearchDashboards } from '../../../../../opensearch_dashboards_react/public'; +import { VisBuilderServices } from '../../../types'; +import { useIndexPatterns } from './use_index_pattern'; + +export const useSampleHits = () => { + const { + services: { + data: { + query: { + filterManager, + queryString, + state$, + timefilter: { timefilter }, + }, + search: { searchSource }, + }, + uiSettings: config, + }, + } = useOpenSearchDashboards(); + const indexPattern = useIndexPatterns().selected; + const [hits, setHits] = useState>>([]); + const [searchContext, setSearchContext] = useState({ + query: queryString.getQuery(), + filters: filterManager.getFilters(), + }); + + useEffect(() => { + async function getData() { + if (indexPattern && searchContext) { + const newSearchSource = await searchSource.create(); + const timeRangeFilter = timefilter.createFilter(indexPattern); + + newSearchSource + .setField('index', indexPattern) + .setField('size', config.get('discover:sampleSize') ?? 500) + .setField('sort', [{ [indexPattern.timeFieldName || '_score']: 'desc' as SortDirection }]) + .setField('filter', [ + ...(searchContext.filters ?? []), + ...(timeRangeFilter ? [timeRangeFilter] : []), + ]); + + if (searchContext.query) { + const contextQuery = + searchContext.query instanceof Array ? searchContext.query[0] : searchContext.query; + + newSearchSource.setField('query', contextQuery); + } + + const searchResponse = await newSearchSource.fetch(); + + setHits(searchResponse.hits.hits); + } + } + + getData(); + }, [config, searchContext, searchSource, indexPattern, timefilter]); + + useLayoutEffect(() => { + const subscription = state$.subscribe(({ state }) => { + setSearchContext({ + query: state.query, + filters: state.filters, + }); + }); + + return () => { + subscription.unsubscribe(); + }; + }, [state$]); + + return hits; +}; diff --git a/src/plugins/vis_builder/public/application/utils/use/use_saved_vis_builder_vis.ts b/src/plugins/vis_builder/public/application/utils/use/use_saved_vis_builder_vis.ts index d7840b92f8ad..6e5d861c5318 100644 --- a/src/plugins/vis_builder/public/application/utils/use/use_saved_vis_builder_vis.ts +++ b/src/plugins/vis_builder/public/application/utils/use/use_saved_vis_builder_vis.ts @@ -13,7 +13,6 @@ import { } from '../../../../../opensearch_dashboards_utils/public'; import { EDIT_PATH, PLUGIN_ID } from '../../../../common'; import { VisBuilderServices } from '../../../types'; -import { MetricOptionsDefaults } from '../../../visualizations/metric/metric_viz_type'; import { getCreateBreadcrumbs, getEditBreadcrumbs } from '../breadcrumbs'; import { getSavedVisBuilderVis } from '../get_saved_vis_builder_vis'; import { @@ -81,7 +80,7 @@ export const useSavedVisBuilderVis = (visualizationIdFromUrl: string | undefined } } - dispatch(setStyleState(styleState)); + dispatch(setStyleState(styleState)); dispatch(setVisualizationState(visualizationState)); } diff --git a/src/plugins/vis_builder/public/visualizations/common/expression_helpers.ts b/src/plugins/vis_builder/public/visualizations/common/expression_helpers.ts index 069666677d60..f50ab9172cdb 100644 --- a/src/plugins/vis_builder/public/visualizations/common/expression_helpers.ts +++ b/src/plugins/vis_builder/public/visualizations/common/expression_helpers.ts @@ -9,8 +9,12 @@ import { ExpressionFunctionOpenSearchDashboards } from '../../../../expressions' import { buildExpressionFunction } from '../../../../expressions/public'; import { VisualizationState } from '../../application/utils/state_management'; import { getSearchService, getIndexPatterns } from '../../plugin_services'; +import { StyleState } from '../../application/utils/state_management'; -export const getAggExpressionFunctions = async (visualization: VisualizationState) => { +export const getAggExpressionFunctions = async ( + visualization: VisualizationState, + style?: StyleState +) => { const { activeVisualization, indexPattern: indexId = '' } = visualization; const { aggConfigParams } = activeVisualization || {}; @@ -32,8 +36,8 @@ export const getAggExpressionFunctions = async (visualization: VisualizationStat 'opensearchaggs', { index: indexId, - metricsAtAllLevels: false, - partialRows: false, + metricsAtAllLevels: style?.showMetricsAtAllLevels || false, + partialRows: style?.showPartialRows || false, aggConfigs: JSON.stringify(aggConfigs.aggs), includeFormatHints: false, } diff --git a/src/plugins/vis_builder/public/visualizations/index.ts b/src/plugins/vis_builder/public/visualizations/index.ts index 6787c28a6ff8..c867e570143e 100644 --- a/src/plugins/vis_builder/public/visualizations/index.ts +++ b/src/plugins/vis_builder/public/visualizations/index.ts @@ -5,6 +5,7 @@ import type { TypeServiceSetup } from '../services/type_service'; import { createMetricConfig } from './metric'; +import { createTableConfig } from './table'; import { createHistogramConfig, createLineConfig, createAreaConfig } from './vislib'; export function registerDefaultTypes(typeServiceSetup: TypeServiceSetup) { @@ -13,6 +14,7 @@ export function registerDefaultTypes(typeServiceSetup: TypeServiceSetup) { createLineConfig, createAreaConfig, createMetricConfig, + createTableConfig, ]; visualizationTypes.forEach((createTypeConfig) => { diff --git a/src/plugins/vis_builder/public/visualizations/table/components/table_viz_options.tsx b/src/plugins/vis_builder/public/visualizations/table/components/table_viz_options.tsx new file mode 100644 index 000000000000..8c934fff8dac --- /dev/null +++ b/src/plugins/vis_builder/public/visualizations/table/components/table_viz_options.tsx @@ -0,0 +1,109 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { get } from 'lodash'; +import React, { useCallback, useEffect, useMemo } from 'react'; +import { i18n } from '@osd/i18n'; +import { FormattedMessage } from '@osd/i18n/react'; +import produce from 'immer'; +import { Draft } from 'immer'; +import { EuiIconTip } from '@elastic/eui'; +import { search } from '../../../../../data/public'; +import { NumberInputOption, SwitchOption } from '../../../../../charts/public'; +import { + useTypedDispatch, + useTypedSelector, + setStyleState, +} from '../../../application/utils/state_management'; +import { TableOptionsDefaults } from '../table_viz_type'; +import { Option } from '../../../application/app'; + +function TableVizOptions() { + const styleState = useTypedSelector((state) => state.style) as TableOptionsDefaults; + const dispatch = useTypedDispatch(); + + const setOption = useCallback( + (callback: (draft: Draft) => void) => { + const newState = produce(styleState, callback); + dispatch(setStyleState(newState)); + }, + [dispatch, styleState] + ); + + const isPerPageValid = styleState.perPage === '' || styleState.perPage > 0; + + return ( + <> + + + ); +} + +export { TableVizOptions }; diff --git a/src/plugins/vis_builder/public/visualizations/table/index.ts b/src/plugins/vis_builder/public/visualizations/table/index.ts new file mode 100644 index 000000000000..51fd19d291e7 --- /dev/null +++ b/src/plugins/vis_builder/public/visualizations/table/index.ts @@ -0,0 +1,6 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export { createTableConfig } from './table_viz_type'; diff --git a/src/plugins/vis_builder/public/visualizations/table/table_viz_type.ts b/src/plugins/vis_builder/public/visualizations/table/table_viz_type.ts new file mode 100644 index 000000000000..733ad986f289 --- /dev/null +++ b/src/plugins/vis_builder/public/visualizations/table/table_viz_type.ts @@ -0,0 +1,95 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { i18n } from '@osd/i18n'; +import { Schemas } from '../../../../vis_default_editor/public'; +import { AggGroupNames } from '../../../../data/public'; +import { TableVizOptions } from './components/table_viz_options'; +import { VisualizationTypeOptions } from '../../services/type_service'; +import { toExpression } from './to_expression'; + +export interface TableOptionsDefaults { + perPage: number | ''; + showPartialRows: boolean; + showMetricsAtAllLevels: boolean; +} + +export const createTableConfig = (): VisualizationTypeOptions => ({ + name: 'table', + title: 'Table', + icon: 'visTable', + description: 'Display table visualizations', + toExpression, + ui: { + containerConfig: { + data: { + schemas: new Schemas([ + { + group: AggGroupNames.Metrics, + name: 'metric', + title: i18n.translate('visTypeTableNewNew.tableVisEditorConfig.schemas.metricTitle', { + defaultMessage: 'Metric', + }), + min: 1, + aggFilter: ['!geo_centroid', '!geo_bounds'], + aggSettings: { + top_hits: { + allowStrings: true, + }, + }, + defaults: { + aggTypes: ['avg', 'cardinality'], + }, + }, + { + group: AggGroupNames.Buckets, + name: 'bucket', + title: i18n.translate('visTypeTableNewNew.tableVisEditorConfig.schemas.bucketTitle', { + defaultMessage: 'Split rows', + }), + aggFilter: ['!filter'], + defaults: { + aggTypes: ['terms'], + }, + }, + { + group: AggGroupNames.Buckets, + name: 'split_row', + title: i18n.translate('visTypeTableNewNew.tableVisEditorConfig.schemas.splitTitle', { + defaultMessage: 'Split table in rows', + }), + min: 0, + max: 1, + aggFilter: ['!filter'], + defaults: { + aggTypes: ['terms'], + }, + }, + { + group: AggGroupNames.Buckets, + name: 'split_column', + title: i18n.translate('visTypeTableNewNew.tableVisEditorConfig.schemas.splitTitle', { + defaultMessage: 'Split table in columns', + }), + min: 0, + max: 1, + aggFilter: ['!filter'], + defaults: { + aggTypes: ['terms'], + }, + }, + ]), + }, + style: { + defaults: { + perPage: 10, + showPartialRows: false, + showMetricsAtAllLevels: false, + }, + render: TableVizOptions, + }, + }, + }, +}); diff --git a/src/plugins/vis_builder/public/visualizations/table/to_expression.ts b/src/plugins/vis_builder/public/visualizations/table/to_expression.ts new file mode 100644 index 000000000000..212c93248d40 --- /dev/null +++ b/src/plugins/vis_builder/public/visualizations/table/to_expression.ts @@ -0,0 +1,130 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { SchemaConfig } from '../../../../visualizations/public'; +import { TableVisExpressionFunctionDefinition } from '../../../../vis_type_table_new/public'; +import { AggConfigs, IAggConfig } from '../../../../data/common'; +import { buildExpression, buildExpressionFunction } from '../../../../expressions/public'; +import { RenderState } from '../../application/utils/state_management'; +import { TableOptionsDefaults } from './table_viz_type'; +import { getAggExpressionFunctions } from '../common/expression_helpers'; + +// TODO: Update to the common getShemas from src/plugins/visualizations/public/legacy/build_pipeline.ts +// And move to a common location accessible by all the visualizations +const getVisSchemas = (aggConfigs: AggConfigs, showMetricsAtAllLevels: boolean): any => { + const createSchemaConfig = (accessor: number, agg: IAggConfig): SchemaConfig => { + const hasSubAgg = [ + 'derivative', + 'moving_avg', + 'serial_diff', + 'cumulative_sum', + 'sum_bucket', + 'avg_bucket', + 'min_bucket', + 'max_bucket', + ].includes(agg.type.name); + + const formatAgg = hasSubAgg + ? agg.params.customMetric || agg.aggConfigs.getRequestAggById(agg.params.metricAgg) + : agg; + + const params = {}; + + const label = agg.makeLabel && agg.makeLabel(); + + return { + accessor, + format: formatAgg.toSerializedFieldFormat(), + params, + label, + aggType: agg.type.name, + }; + }; + + let cnt = 0; + const schemas: any = { + metric: [], + }; + + if (!aggConfigs) { + return schemas; + } + + const responseAggs = aggConfigs.getResponseAggs().filter((agg: IAggConfig) => agg.enabled); + const metrics = responseAggs.filter((agg: IAggConfig) => agg.type.type === 'metrics'); + + responseAggs.forEach((agg) => { + let skipMetrics = false; + const schemaName = agg.schema; + + if (!schemaName) { + cnt++; + return; + } + + if (schemaName === 'split_row' || schemaName === 'split_column') { + skipMetrics = responseAggs.length - metrics.length > 1; + } + + if (!schemas[schemaName]) { + schemas[schemaName] = []; + } + + if (!showMetricsAtAllLevels || agg.type.type !== 'metrics') { + schemas[schemaName]!.push(createSchemaConfig(cnt++, agg)); + } + + if ( + showMetricsAtAllLevels && + (agg.type.type !== 'metrics' || metrics.length === responseAggs.length) + ) { + metrics.forEach((metric: any) => { + const schemaConfig = createSchemaConfig(cnt++, metric); + if (!skipMetrics) { + schemas.metric.push(schemaConfig); + } + }); + } + }); + + return schemas; +}; + +export interface TableRootState extends RenderState { + style: TableOptionsDefaults; +} + +export const toExpression = async ({ style: styleState, visualization }: TableRootState) => { + const { aggConfigs, expressionFns } = await getAggExpressionFunctions(visualization, styleState); + const { showPartialRows, showMetricsAtAllLevels } = styleState; + + const schemas = getVisSchemas(aggConfigs, showMetricsAtAllLevels); + + const metrics = + schemas.bucket && showPartialRows && !showMetricsAtAllLevels + ? schemas.metric.slice(-1 * (schemas.metric.length / schemas.bucket.length)) + : schemas.metric; + + const tableData = { + metrics, + buckets: schemas.bucket || [], + splitRow: schemas.split_row, + splitColumn: schemas.split_column, + }; + + const visConfig = { + ...styleState, + ...tableData, + }; + + const tableVis = buildExpressionFunction( + 'opensearch_dashboards_table_new', + { + visConfig: JSON.stringify(visConfig), + } + ); + + return buildExpression([...expressionFns, tableVis]).toString(); +}; diff --git a/src/plugins/vis_type_table_new/README.md b/src/plugins/vis_type_table_new/README.md new file mode 100644 index 000000000000..06299ed963a2 --- /dev/null +++ b/src/plugins/vis_type_table_new/README.md @@ -0,0 +1 @@ +Contains the data table visualization, that allows presenting data using a Datagrid component. diff --git a/src/plugins/vis_type_table_new/opensearch_dashboards.json b/src/plugins/vis_type_table_new/opensearch_dashboards.json new file mode 100644 index 000000000000..598ca7581b83 --- /dev/null +++ b/src/plugins/vis_type_table_new/opensearch_dashboards.json @@ -0,0 +1,16 @@ +{ + "id": "visTypeTableNew", + "version": "opensearchDashboards", + "server": false, + "ui": true, + "requiredPlugins": [ + "expressions", + "visualizations", + "data" + ], + "requiredBundles": [ + "opensearchDashboardsUtils", + "opensearchDashboardsReact", + "share" + ] +} diff --git a/src/plugins/vis_type_table_new/public/components/table_vis_app.scss b/src/plugins/vis_type_table_new/public/components/table_vis_app.scss new file mode 100644 index 000000000000..af6558774da3 --- /dev/null +++ b/src/plugins/vis_type_table_new/public/components/table_vis_app.scss @@ -0,0 +1,14 @@ +.visTable__group { + padding: $euiSizeS; + margin-bottom: $euiSizeL; + + > h3 { + text-align: center; + } +} + +.visTable__groupInColumns { + display: flex; + flex-direction: row; + align-items: flex-start; +} diff --git a/src/plugins/vis_type_table_new/public/components/table_vis_app.tsx b/src/plugins/vis_type_table_new/public/components/table_vis_app.tsx new file mode 100644 index 000000000000..7958b2187620 --- /dev/null +++ b/src/plugins/vis_type_table_new/public/components/table_vis_app.tsx @@ -0,0 +1,71 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import './table_vis_app.scss'; +import React, { useEffect, useState } from 'react'; +import classNames from 'classnames'; +import { CoreStart } from 'opensearch-dashboards/public'; +import { I18nProvider } from '@osd/i18n/react'; +import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; +import { OpenSearchDashboardsContextProvider } from '../../../opensearch_dashboards_react/public'; +import { TableContext } from '../table_vis_response_handler'; +import { TableVisConfig, SortColumn, ColumnWidth, TableUiState } from '../types'; +import { TableVisComponent } from './table_vis_component'; +import { TableVisComponentGroup } from './table_vis_component_group'; + +interface TableVisAppProps { + services: CoreStart; + visData: TableContext; + visConfig: TableVisConfig; + handlers: IInterpreterRenderHandlers; +} + +export const TableVisApp = ({ + services, + visData: { table, tableGroups, direction }, + visConfig, + handlers, +}: TableVisAppProps) => { + // Rendering is asynchronous, completed by handlers.done() + useEffect(() => { + handlers.done(); + }, [handlers]); + + const className = classNames('visTable', { + // eslint-disable-next-line @typescript-eslint/naming-convention + visTable__groupInColumns: direction === 'column', + }); + + // TODO: remove duplicate sort and width state + // Issue: https://github.com/opensearch-project/OpenSearch-Dashboards/issues/2704#issuecomment-1299380818 + const [sort, setSort] = useState({ colIndex: null, direction: null }); + const [width, setWidth] = useState([]); + + const tableUiState: TableUiState = { sort, setSort, width, setWidth }; + + return ( + + +
+ {table ? ( + + ) : ( + + )} +
+
+
+ ); +}; diff --git a/src/plugins/vis_type_table_new/public/components/table_vis_component.tsx b/src/plugins/vis_type_table_new/public/components/table_vis_component.tsx new file mode 100644 index 000000000000..4a25395703b0 --- /dev/null +++ b/src/plugins/vis_type_table_new/public/components/table_vis_component.tsx @@ -0,0 +1,146 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useCallback, useMemo } from 'react'; +import { orderBy } from 'lodash'; +import { EuiDataGridProps, EuiDataGrid, EuiDataGridSorting, EuiTitle } from '@elastic/eui'; + +import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; +import { Table } from '../table_vis_response_handler'; +import { TableVisConfig, ColumnWidth, SortColumn, TableUiState } from '../types'; +import { getDataGridColumns } from './table_vis_grid_columns'; +import { usePagination } from '../utils'; +import { convertToFormattedData } from '../utils/convert_to_formatted_data'; +import { TableVisControl } from './table_vis_control'; + +interface TableVisComponentProps { + title?: string; + table: Table; + visConfig: TableVisConfig; + event: IInterpreterRenderHandlers['event']; + uiState: TableUiState; +} + +export const TableVisComponent = ({ + title, + table, + visConfig, + event, + uiState, +}: TableVisComponentProps) => { + const { formattedRows: rows, formattedColumns: columns } = convertToFormattedData( + table, + visConfig + ); + + const pagination = usePagination(visConfig, rows.length); + + const sortedRows = useMemo(() => { + return uiState.sort.colIndex !== null && + columns[uiState.sort.colIndex].id && + uiState.sort.direction + ? orderBy(rows, columns[uiState.sort.colIndex].id, uiState.sort.direction) + : rows; + }, [columns, rows, uiState]); + + const renderCellValue = useMemo(() => { + return (({ rowIndex, columnId }) => { + const rawContent = sortedRows[rowIndex][columnId]; + const colIndex = columns.findIndex((col) => col.id === columnId); + const column = columns[colIndex]; + // use formatter to format raw content + // this can format date and percentage data + const formattedContent = column.formatter.convert(rawContent, 'text'); + return sortedRows.hasOwnProperty(rowIndex) ? formattedContent || null : null; + }) as EuiDataGridProps['renderCellValue']; + }, [sortedRows, columns]); + + const dataGridColumns = getDataGridColumns(sortedRows, columns, table, event, uiState.width); + + const sortedColumns = useMemo(() => { + return uiState.sort.colIndex !== null && + dataGridColumns[uiState.sort.colIndex].id && + uiState.sort.direction + ? [{ id: dataGridColumns[uiState.sort.colIndex].id, direction: uiState.sort.direction }] + : []; + }, [dataGridColumns, uiState]); + + const onSort = useCallback( + (sortingCols: EuiDataGridSorting['columns'] | []) => { + const nextSortValue = sortingCols[sortingCols.length - 1]; + const nextSort: SortColumn = + sortingCols.length > 0 + ? { + colIndex: dataGridColumns.findIndex((col) => col.id === nextSortValue?.id), + direction: nextSortValue.direction, + } + : { + colIndex: null, + direction: null, + }; + uiState.setSort(nextSort); + return nextSort; + }, + [dataGridColumns, uiState] + ); + + const onColumnResize: EuiDataGridProps['onColumnResize'] = useCallback( + ({ columnId, width }) => { + const curWidth: ColumnWidth[] = uiState.width; + const nextWidth = [...curWidth]; + const nextColIndex = columns.findIndex((col) => col.id === columnId); + const curColIndex = curWidth.findIndex((col) => col.colIndex === nextColIndex); + const nextColWidth = { colIndex: nextColIndex, width }; + + // if updated column index is not found, then add it to nextWidth + // else reset it in nextWidth + if (curColIndex < 0) nextWidth.push(nextColWidth); + else nextWidth[curColIndex] = nextColWidth; + + // update uiState.width + uiState.setWidth(nextWidth); + }, + [columns, uiState] + ); + + const ariaLabel = title || visConfig.title || 'tableVis'; + + return ( + <> + {title && ( + +

{title}

+
+ )} + id), + setVisibleColumns: () => {}, + }} + rowCount={rows.length} + renderCellValue={renderCellValue} + sorting={{ columns: sortedColumns, onSort }} + onColumnResize={onColumnResize} + pagination={pagination} + gridStyle={{ + border: 'horizontal', + header: 'underline', + }} + minSizeForControls={1} + toolbarVisibility={{ + showColumnSelector: false, + showSortSelector: false, + showFullScreenSelector: false, + showStyleSelector: false, + additionalControls: ( + + ), + }} + /> + + ); +}; diff --git a/src/plugins/vis_type_table_new/public/components/table_vis_component_group.tsx b/src/plugins/vis_type_table_new/public/components/table_vis_component_group.tsx new file mode 100644 index 000000000000..633b9d2230bd --- /dev/null +++ b/src/plugins/vis_type_table_new/public/components/table_vis_component_group.tsx @@ -0,0 +1,38 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { memo } from 'react'; + +import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; +import { TableGroup } from '../table_vis_response_handler'; +import { TableVisConfig, TableUiState } from '../types'; +import { TableVisComponent } from './table_vis_component'; + +interface TableVisGroupComponentProps { + tableGroups: TableGroup[]; + visConfig: TableVisConfig; + event: IInterpreterRenderHandlers['event']; + uiState: TableUiState; +} + +export const TableVisComponentGroup = memo( + ({ tableGroups, visConfig, event, uiState }: TableVisGroupComponentProps) => { + return ( + <> + {tableGroups.map(({ tables, title }) => ( +
+ +
+ ))} + + ); + } +); diff --git a/src/plugins/vis_type_table_new/public/components/table_vis_control.tsx b/src/plugins/vis_type_table_new/public/components/table_vis_control.tsx new file mode 100644 index 000000000000..26b51c9cc85b --- /dev/null +++ b/src/plugins/vis_type_table_new/public/components/table_vis_control.tsx @@ -0,0 +1,55 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useState } from 'react'; +import { EuiPopover, EuiButtonEmpty, EuiContextMenuPanel, EuiContextMenuItem } from '@elastic/eui'; +import { OpenSearchDashboardsDatatableRow } from 'src/plugins/expressions'; +import { CoreStart } from 'opensearch-dashboards/public'; +import { exportAsCsv } from '../utils/convert_to_csv_data'; +import { FormattedColumn } from '../types'; +import { useOpenSearchDashboards } from '../../../../../src/plugins/opensearch_dashboards_react/public'; + +interface TableVisControlProps { + filename?: string; + rows: OpenSearchDashboardsDatatableRow[]; + columns: FormattedColumn[]; +} + +export const TableVisControl = (props: TableVisControlProps) => { + const { + services: { uiSettings }, + } = useOpenSearchDashboards(); + const [isPopoverOpen, setPopover] = useState(false); + + return ( + setPopover((open) => !open)} /> + } + isOpen={isPopoverOpen} + closePopover={() => setPopover(false)} + panelPaddingSize="none" + > + exportAsCsv(false, { ...props, uiSettings })} + > + Raw + , + exportAsCsv(true, { ...props, uiSettings })} + > + Formatted + , + ]} + /> + + ); +}; diff --git a/src/plugins/vis_type_table_new/public/components/table_vis_grid_columns.tsx b/src/plugins/vis_type_table_new/public/components/table_vis_grid_columns.tsx new file mode 100644 index 000000000000..ba204ea6ae33 --- /dev/null +++ b/src/plugins/vis_type_table_new/public/components/table_vis_grid_columns.tsx @@ -0,0 +1,148 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { i18n } from '@osd/i18n'; +import { EuiDataGridColumn, EuiDataGridColumnCellActionProps } from '@elastic/eui'; +import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; +import { OpenSearchDashboardsDatatableRow } from 'src/plugins/expressions'; +import { Table } from '../table_vis_response_handler'; +import { ColumnWidth, FormattedColumn } from '../types'; + +export const getDataGridColumns = ( + rows: OpenSearchDashboardsDatatableRow[], + cols: FormattedColumn[], + table: Table, + event: IInterpreterRenderHandlers['event'], + columnsWidth: ColumnWidth[] +) => { + const filterBucket = (rowIndex: number, columnIndex: number, negate: boolean) => { + const foramttedColumnId = cols[columnIndex].id; + const rawColumnIndex = table.columns.findIndex((col) => col.id === foramttedColumnId); + event({ + name: 'filterBucket', + data: { + data: [ + { + table: { + columns: table.columns, + rows, + }, + row: rowIndex, + column: rawColumnIndex, + }, + ], + negate, + }, + }); + }; + + return cols.map((col, colIndex) => { + // const cellActions = col.filterable + // ? [ + // ({ rowIndex, columnId, Component, closePopover }: EuiDataGridColumnCellActionProps) => { + // const filterValue = rows[rowIndex][columnId]; + // const filterContent = col.formatter?.convert(filterValue); + + // const filterForValueText = i18n.translate( + // 'visTypeTableNew.tableVisFilter.filterForValue', + // { + // defaultMessage: 'Filter for value', + // } + // ); + // const filterForValueLabel = i18n.translate( + // 'visTypeTableNew.tableVisFilter.filterForValueLabel', + // { + // defaultMessage: 'Filter for value: {filterContent}', + // values: { + // filterContent, + // }, + // } + // ); + + // return ( + // filterValue != null && ( + // { + // filterBucket(rowIndex, colIndex, false); + // closePopover(); + // }} + // iconType="plusInCircle" + // aria-label={filterForValueLabel} + // data-test-subj="filterForValue" + // > + // {filterForValueText} + // + // ) + // ); + // }, + // ({ rowIndex, columnId, Component, closePopover }: EuiDataGridColumnCellActionProps) => { + // const filterValue = rows[rowIndex][columnId]; + // const filterContent = col.formatter?.convert(filterValue); + + // const filterOutValueText = i18n.translate( + // 'visTypeTableNew.tableVisFilter.filterOutValue', + // { + // defaultMessage: 'Filter out value', + // } + // ); + // const filterOutValueLabel = i18n.translate( + // 'visTypeTableNew.tableVisFilter.filterOutValueLabel', + // { + // defaultMessage: 'Filter out value: {filterContent}', + // values: { + // filterContent, + // }, + // } + // ); + + // return ( + // filterValue != null && ( + // { + // filterBucket(rowIndex, colIndex, true); + // closePopover(); + // }} + // iconType="minusInCircle" + // aria-label={filterOutValueLabel} + // data-test-subj="filterOutValue" + // > + // {filterOutValueText} + // + // ) + // ); + // }, + // ] + // : undefined; + + const initialWidth = columnsWidth.find((c) => c.colIndex === colIndex); + + const dataGridColumn: EuiDataGridColumn = { + id: col.id, + display: col.title, + displayAsText: col.title, + actions: { + showHide: false, + showMoveLeft: false, + showMoveRight: false, + showSortAsc: { + label: i18n.translate('visTypeTableNew.tableVisSort.ascSortLabel', { + defaultMessage: 'Sort asc', + }), + }, + showSortDesc: { + label: i18n.translate('visTypeTableNew.tableVisSort.descSortLabel', { + defaultMessage: 'Sort desc', + }), + }, + }, + // cellActions, + }; + if (initialWidth) { + dataGridColumn.initialWidth = initialWidth.width; + } + return dataGridColumn; + }); +}; diff --git a/src/plugins/vis_type_table_new/public/index.ts b/src/plugins/vis_type_table_new/public/index.ts new file mode 100644 index 000000000000..4ed30b71eeaa --- /dev/null +++ b/src/plugins/vis_type_table_new/public/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +// import { PluginInitializerContext } from 'opensearch-dashboards/public'; +import { TableVisPlugin as Plugin } from './plugin'; + +export function plugin() { + return new Plugin(); +} +/* Public Types */ +export { TableVisExpressionFunctionDefinition } from './table_vis_fn'; diff --git a/src/plugins/vis_type_table_new/public/plugin.ts b/src/plugins/vis_type_table_new/public/plugin.ts new file mode 100644 index 000000000000..9cc96c3e9895 --- /dev/null +++ b/src/plugins/vis_type_table_new/public/plugin.ts @@ -0,0 +1,36 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { CoreSetup, CoreStart, Plugin } from 'opensearch-dashboards/public'; +import { Plugin as ExpressionsPublicPlugin } from '../../expressions/public'; + +import { createTableVisFn } from './table_vis_fn'; +import { DataPublicPluginStart } from '../../data/public'; +import { setFormatService } from './services'; +import { getTableVisRenderer } from './table_vis_renderer'; + +export interface TableVisPluginSetupDependencies { + expressions: ReturnType; +} + +export interface TableVisPluginStartDependencies { + data: DataPublicPluginStart; +} + +const setupTableVis = async (core: CoreSetup, { expressions }: TableVisPluginSetupDependencies) => { + const [coreStart] = await core.getStartServices(); + expressions.registerFunction(createTableVisFn); + expressions.registerRenderer(getTableVisRenderer(coreStart)); +}; + +export class TableVisPlugin implements Plugin { + public async setup(core: CoreSetup, dependencies: TableVisPluginSetupDependencies) { + setupTableVis(core, dependencies); + } + + public start(core: CoreStart, { data }: TableVisPluginStartDependencies) { + setFormatService(data.fieldFormats); + } +} diff --git a/src/plugins/vis_type_table_new/public/services.ts b/src/plugins/vis_type_table_new/public/services.ts new file mode 100644 index 000000000000..f8ca4b574307 --- /dev/null +++ b/src/plugins/vis_type_table_new/public/services.ts @@ -0,0 +1,11 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { createGetterSetter } from '../../opensearch_dashboards_utils/public'; +import { DataPublicPluginStart } from '../../data/public'; + +export const [getFormatService, setFormatService] = createGetterSetter< + DataPublicPluginStart['fieldFormats'] +>('table data.fieldFormats'); diff --git a/src/plugins/vis_type_table_new/public/table_vis_fn.ts b/src/plugins/vis_type_table_new/public/table_vis_fn.ts new file mode 100644 index 000000000000..ec9eafc344af --- /dev/null +++ b/src/plugins/vis_type_table_new/public/table_vis_fn.ts @@ -0,0 +1,65 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { i18n } from '@osd/i18n'; +import { tableVisResponseHandler, TableContext } from './table_vis_response_handler'; +import { + ExpressionFunctionDefinition, + OpenSearchDashboardsDatatable, + Render, +} from '../../expressions/public'; +import { TableVisConfig } from './types'; + +export type Input = OpenSearchDashboardsDatatable; + +interface Arguments { + visConfig: string | null; +} + +export interface TableVisRenderValue { + visData: TableContext; + visType: 'table'; + visConfig: TableVisConfig; +} + +export type TableVisExpressionFunctionDefinition = ExpressionFunctionDefinition< + 'opensearch_dashboards_table_new', + Input, + Arguments, + Render +>; + +export const createTableVisFn = (): TableVisExpressionFunctionDefinition => ({ + name: 'opensearch_dashboards_table_new', + type: 'render', + inputTypes: ['opensearch_dashboards_datatable'], + help: i18n.translate('visTypeTableNew.function.help', { + defaultMessage: 'Table visualization', + }), + args: { + visConfig: { + types: ['string', 'null'], + default: '"{}"', + help: '', + }, + }, + fn(input, args) { + const visConfig = args.visConfig && JSON.parse(args.visConfig); + const convertedData = tableVisResponseHandler(input, visConfig); + + return { + type: 'render', + as: 'table_vis', + value: { + visData: convertedData, + visType: 'table', + visConfig, + }, + params: { + listenOnChange: true, + }, + }; + }, +}); diff --git a/src/plugins/vis_type_table_new/public/table_vis_renderer.tsx b/src/plugins/vis_type_table_new/public/table_vis_renderer.tsx new file mode 100644 index 000000000000..8e467112528d --- /dev/null +++ b/src/plugins/vis_type_table_new/public/table_vis_renderer.tsx @@ -0,0 +1,36 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; + +import { CoreStart } from 'opensearch-dashboards/public'; +import { VisualizationContainer } from '../../visualizations/public'; +import { ExpressionRenderDefinition } from '../../expressions/common/expression_renderers'; +import { TableVisRenderValue } from './table_vis_fn'; +import { TableVisApp } from './components/table_vis_app'; + +export const getTableVisRenderer: ( + core: CoreStart +) => ExpressionRenderDefinition = (core) => ({ + name: 'table_vis', + displayName: 'table visualization', + reuseDomNode: true, + render: async (domNode, { visData, visConfig }, handlers) => { + handlers.onDestroy(() => { + unmountComponentAtNode(domNode); + }); + + const showNoResult = visData.table + ? visData.table.rows.length === 0 + : visData.tableGroups?.length === 0; + render( + + + , + domNode + ); + }, +}); diff --git a/src/plugins/vis_type_table_new/public/table_vis_response_handler.ts b/src/plugins/vis_type_table_new/public/table_vis_response_handler.ts new file mode 100644 index 000000000000..b1d41edfff8b --- /dev/null +++ b/src/plugins/vis_type_table_new/public/table_vis_response_handler.ts @@ -0,0 +1,112 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * 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 { getFormatService } from './services'; +import { OpenSearchDashboardsDatatable } from '../../expressions/public'; +import { TableVisConfig } from './types'; + +export interface Table { + columns: OpenSearchDashboardsDatatable['columns']; + rows: OpenSearchDashboardsDatatable['rows']; +} + +export interface TableGroup { + table: OpenSearchDashboardsDatatable; + tables: Table[]; + title: string; + name: string; + key: any; + column: number; + row: number; +} + +export interface TableContext { + table?: Table; + tableGroups: TableGroup[]; + direction?: 'row' | 'column'; +} + +export function tableVisResponseHandler( + input: OpenSearchDashboardsDatatable, + config: TableVisConfig +): TableContext { + let table: Table | undefined; + const tableGroups: TableGroup[] = []; + let direction: TableContext['direction']; + + const split = config.splitColumn || config.splitRow; + + if (split) { + direction = config.splitRow ? 'row' : 'column'; + const splitColumnIndex = split[0].accessor; + const splitColumnFormatter = getFormatService().deserialize(split[0].format); + const splitColumn = input.columns[splitColumnIndex]; + const splitMap: { [key: string]: number } = {}; + let splitIndex = 0; + + input.rows.forEach((row, rowIndex) => { + const splitValue: any = row[splitColumn.id]; + + if (!splitMap.hasOwnProperty(splitValue as any)) { + (splitMap as any)[splitValue] = splitIndex++; + const tableGroup: TableGroup = { + title: `${splitColumnFormatter.convert(splitValue)}: ${splitColumn.name}`, + name: splitColumn.name, + key: splitValue, + column: splitColumnIndex, + row: rowIndex, + table: input, + tables: [], + }; + + tableGroup.tables.push({ + columns: input.columns, + rows: [], + }); + + tableGroups.push(tableGroup); + } + + const tableIndex = (splitMap as any)[splitValue]; + (tableGroups[tableIndex] as any).tables[0].rows.push(row); + }); + } else { + table = { + columns: input.columns, + rows: input.rows, + }; + } + + return { + table, + tableGroups, + direction, + }; +} diff --git a/src/plugins/vis_type_table_new/public/types.ts b/src/plugins/vis_type_table_new/public/types.ts new file mode 100644 index 000000000000..0c5a9f9955e0 --- /dev/null +++ b/src/plugins/vis_type_table_new/public/types.ts @@ -0,0 +1,70 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * 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 { SchemaConfig } from 'src/plugins/visualizations/public'; +import { IFieldFormat } from 'src/plugins/data/public'; + +export interface TableVisConfig extends TableVisParams { + title: string; + metrics: SchemaConfig[]; + buckets: SchemaConfig[]; + splitRow?: SchemaConfig[]; + splitColumn?: SchemaConfig[]; +} + +export interface TableVisParams { + perPage: number | ''; + showPartialRows: boolean; + showMetricsAtAllLevels: boolean; +} + +export interface FormattedColumn { + id: string; + title: string; + formatter: IFieldFormat; + filterable: boolean; +} + +export interface ColumnWidth { + colIndex: number; + width: number; +} + +export interface SortColumn { + colIndex: number | null; + direction: 'asc' | 'desc' | null; +} + +export interface TableUiState { + sort: SortColumn; + setSort: (sort: SortColumn) => void; + width: ColumnWidth[]; + setWidth: (columnsWidth: ColumnWidth[]) => void; +} diff --git a/src/plugins/vis_type_table_new/public/utils/convert_to_csv_data.ts b/src/plugins/vis_type_table_new/public/utils/convert_to_csv_data.ts new file mode 100644 index 000000000000..2c37df1aa3d5 --- /dev/null +++ b/src/plugins/vis_type_table_new/public/utils/convert_to_csv_data.ts @@ -0,0 +1,85 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * 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 { isObject } from 'lodash'; +// @ts-ignore +import { saveAs } from '@elastic/filesaver'; +import { CoreStart } from 'opensearch-dashboards/public'; +import { CSV_SEPARATOR_SETTING, CSV_QUOTE_VALUES_SETTING } from '../../../share/public'; +import { OpenSearchDashboardsDatatable } from '../../../expressions/public'; +import { FormattedColumn } from '../types'; + +const nonAlphaNumRE = /[^a-zA-Z0-9]/; +const allDoubleQuoteRE = /"/g; + +interface CSVDataProps { + filename?: string; + rows: OpenSearchDashboardsDatatable['rows']; + columns: FormattedColumn[]; + uiSettings: CoreStart['uiSettings']; +} + +const toCsv = function (formatted: boolean, { rows, columns, uiSettings }: CSVDataProps) { + const separator = uiSettings.get(CSV_SEPARATOR_SETTING); + const quoteValues = uiSettings.get(CSV_QUOTE_VALUES_SETTING); + + function escape(val: any) { + if (!formatted && isObject(val)) val = val.valueOf(); + val = String(val); + if (quoteValues && nonAlphaNumRE.test(val)) { + val = '"' + val.replace(allDoubleQuoteRE, '""') + '"'; + } + return val; + } + + let csvRows: string[][] = []; + for (const row of rows) { + const rowArray = []; + for (const col of columns) { + const value = row[col.id]; + const formattedValue = + formatted && col.formatter ? escape(col.formatter.convert(value)) : escape(value); + rowArray.push(formattedValue); + } + csvRows = [...csvRows, rowArray]; + } + + // add the columns to the rows + csvRows.unshift(columns.map((col) => escape(col.title))); + + return csvRows.map((row) => row.join(separator) + '\r\n').join(''); +}; + +export const exportAsCsv = function (formatted: boolean, csvData: CSVDataProps) { + const csv = new Blob([toCsv(formatted, csvData)], { type: 'text/csv;charset=utf-8' }); + const type = formatted ? 'formatted' : 'raw'; + if (csvData.filename) saveAs(csv, `${csvData.filename}-${type}.csv`); + else saveAs(csv, `unsaved-${type}.csv`); +}; diff --git a/src/plugins/vis_type_table_new/public/utils/convert_to_formatted_data.ts b/src/plugins/vis_type_table_new/public/utils/convert_to_formatted_data.ts new file mode 100644 index 000000000000..cd997dfe5d5e --- /dev/null +++ b/src/plugins/vis_type_table_new/public/utils/convert_to_formatted_data.ts @@ -0,0 +1,68 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Any modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +/* + * 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 { OpenSearchDashboardsDatatableRow } from 'src/plugins/expressions'; +import { Table } from '../table_vis_response_handler'; +import { TableVisConfig } from '../types'; +import { getFormatService } from '../services'; +import { FormattedColumn } from '../types'; +export interface FormattedDataProps { + formattedRows: OpenSearchDashboardsDatatableRow[]; + formattedColumns: FormattedColumn[]; +} + +export const convertToFormattedData = ( + table: Table, + visConfig: TableVisConfig +): FormattedDataProps => { + const { buckets, metrics } = visConfig; + const formattedRows: OpenSearchDashboardsDatatableRow[] = table.rows; + const formattedColumns: FormattedColumn[] = table.columns + .map(function (col, i) { + const isBucket = buckets.find((bucket) => bucket.accessor === i); + const dimension = isBucket || metrics.find((metric) => metric.accessor === i); + + if (!dimension) return undefined; + + const formatter = getFormatService().deserialize(dimension.format); + + const formattedColumn: FormattedColumn = { + id: col.id, + title: col.name, + formatter, + filterable: !!isBucket, + }; + + return formattedColumn; + }) + .filter((column): column is FormattedColumn => !!column); + + return { formattedRows, formattedColumns }; +}; diff --git a/src/plugins/vis_type_table_new/public/utils/index.ts b/src/plugins/vis_type_table_new/public/utils/index.ts new file mode 100644 index 000000000000..1fd0e3f1e0fd --- /dev/null +++ b/src/plugins/vis_type_table_new/public/utils/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export * from './convert_to_csv_data'; +export * from './convert_to_formatted_data'; +export * from './use_pagination'; diff --git a/src/plugins/vis_type_table_new/public/utils/use_pagination.ts b/src/plugins/vis_type_table_new/public/utils/use_pagination.ts new file mode 100644 index 000000000000..45dbed2c0da8 --- /dev/null +++ b/src/plugins/vis_type_table_new/public/utils/use_pagination.ts @@ -0,0 +1,39 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { TableVisConfig } from '../types'; + +export const usePagination = (visConfig: TableVisConfig, nRow: number) => { + const [pagination, setPagination] = useState({ + pageIndex: 0, + pageSize: visConfig.perPage || 10, + }); + const onChangeItemsPerPage = useCallback( + (pageSize) => setPagination((p) => ({ ...p, pageSize, pageIndex: 0 })), + [setPagination] + ); + const onChangePage = useCallback((pageIndex) => setPagination((p) => ({ ...p, pageIndex })), [ + setPagination, + ]); + + useEffect(() => { + const perPage = visConfig.perPage || 10; + const maxiPageIndex = Math.ceil(nRow / perPage) - 1; + setPagination((p) => ({ + pageIndex: p.pageIndex > maxiPageIndex ? maxiPageIndex : p.pageIndex, + pageSize: perPage, + })); + }, [nRow, visConfig.perPage]); + + return useMemo( + () => ({ + ...pagination, + onChangeItemsPerPage, + onChangePage, + }), + [pagination, onChangeItemsPerPage, onChangePage] + ); +}; diff --git a/test/functional/apps/management/_scripted_fields.js b/test/functional/apps/management/_scripted_fields.js index cbd1169e7a33..9ce2a57436e1 100644 --- a/test/functional/apps/management/_scripted_fields.js +++ b/test/functional/apps/management/_scripted_fields.js @@ -509,10 +509,10 @@ export default function ({ getService, getPageObjects }) { it('should filter by scripted field value in Discover', async function () { await PageObjects.discover.clickFieldListItem(scriptedPainlessFieldName2); - await log.debug('filter by "Sep 17, 2015 @ 23:00" in the expanded scripted field list'); + await log.debug('filter by "Sep 18, 2015 @ 7:52" in the expanded scripted field list'); await PageObjects.discover.clickFieldListPlusFilter( scriptedPainlessFieldName2, - '1442531297065' + '1442562775953' ); await PageObjects.header.waitUntilLoadingHasFinished(); diff --git a/yarn.lock b/yarn.lock index 7c7726328e15..822684d097bd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1331,9 +1331,9 @@ "@hapi/validate" "1.x.x" "@elastic/makelogs@^6.1.0": - version "6.1.0" - resolved "https://registry.yarnpkg.com/@elastic/makelogs/-/makelogs-6.1.0.tgz#1ea61a01b4680c0e904c5b8be3d325f427030e16" - integrity sha512-iFhSpgOcmv7q65AiTzny2fkhddVdG/6yoX7pUsvwJo9Cc0B6ySVOlwweAWntkwQC3Y1IQpm9gpQUe2GwsyRUkQ== + version "6.1.1" + resolved "https://registry.yarnpkg.com/@elastic/makelogs/-/makelogs-6.1.1.tgz#5bc173b16acdfd7844fd85c97824ddcffd67cfb3" + integrity sha512-cmfXFQITwyT4SV+Ryerg/vVbGQ9E2BhYKQ9flG85Ba3blGVmOjkgv7TYQam6xAIvGXFGBBrcyqEwmuw7xZ5ZNQ== dependencies: async "^1.4.2" commander "^5.0.0" @@ -1342,7 +1342,6 @@ moment "^2.10.6" progress "^1.1.8" through2 "^2.0.0" - update-notifier "^0.5.0" "@elastic/node-crypto@1.1.1": version "1.1.1" @@ -4418,10 +4417,10 @@ angular@>=1.0.6, angular@^1.8.2: resolved "https://registry.yarnpkg.com/angular/-/angular-1.8.2.tgz#5983bbb5a9fa63e213cb7749199e0d352de3a2f1" integrity sha512-IauMOej2xEe7/7Ennahkbb5qd/HFADiNuLSESz9Q27inmi32zB0lnAsFeLEWcox3Gd1F6YhNd1CP7/9IukJ0Gw== -ansi-colors@3.2.3: - version "3.2.3" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.3.tgz#57d35b8686e851e2cc04c403f1c00203976a1813" - integrity sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw== +ansi-colors@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" + integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== ansi-colors@^1.0.1: version "1.1.0" @@ -4506,7 +4505,7 @@ anymatch@^2.0.0: micromatch "^3.1.4" normalize-path "^2.1.1" -anymatch@^3.0.0, anymatch@^3.0.3, anymatch@~3.1.1, anymatch@~3.1.2: +anymatch@^3.0.0, anymatch@^3.0.3, anymatch@~3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== @@ -5566,7 +5565,7 @@ camelcase@^5.0.0, camelcase@^5.3.1: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -camelcase@^6.2.0: +camelcase@^6.0.0, camelcase@^6.2.0: version "6.3.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== @@ -5728,22 +5727,7 @@ cheerio@^1.0.0-rc.3: parse5-htmlparser2-tree-adapter "^6.0.1" tslib "^2.2.0" -chokidar@3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.0.tgz#12c0714668c55800f659e262d4962a97faf554a6" - integrity sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A== - dependencies: - anymatch "~3.1.1" - braces "~3.0.2" - glob-parent "~5.1.0" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.2.0" - optionalDependencies: - fsevents "~2.1.1" - -"chokidar@>=2.0.0 <4.0.0", chokidar@^3.4.0, chokidar@^3.4.1, chokidar@^3.4.2: +chokidar@3.5.3, "chokidar@>=2.0.0 <4.0.0", chokidar@^3.4.0, chokidar@^3.4.1, chokidar@^3.4.2: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== @@ -5888,15 +5872,6 @@ cli-width@^3.0.0: resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== -cliui@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" - integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== - dependencies: - string-width "^3.1.0" - strip-ansi "^5.2.0" - wrap-ansi "^5.1.0" - cliui@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" @@ -6161,20 +6136,6 @@ concat-stream@^1.4.7, concat-stream@^1.5.0: readable-stream "^2.2.2" typedarray "^0.0.6" -configstore@^1.0.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/configstore/-/configstore-1.4.0.tgz#c35781d0501d268c25c54b8b17f6240e8a4fb021" - integrity sha1-w1eB0FAdJowlxUuLF/YkDopPsCE= - dependencies: - graceful-fs "^4.1.2" - mkdirp "^0.5.0" - object-assign "^4.0.1" - os-tmpdir "^1.0.0" - osenv "^0.1.0" - uuid "^2.0.1" - write-file-atomic "^1.1.2" - xdg-basedir "^2.0.0" - console-browserify@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" @@ -6867,13 +6828,6 @@ debug@3.1.0: dependencies: ms "2.0.0" -debug@3.2.6: - version "3.2.6" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" - integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== - dependencies: - ms "^2.1.1" - debug@3.X, debug@^3.2.6, debug@^3.2.7: version "3.2.7" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" @@ -6881,7 +6835,7 @@ debug@3.X, debug@^3.2.6, debug@^3.2.7: dependencies: ms "^2.1.1" -debug@4, debug@^4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4: +debug@4, debug@4.3.4, debug@^4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -6920,6 +6874,11 @@ decamelize@^1.1.0, decamelize@^1.2.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= +decamelize@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" + integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== + decimal.js@^10.2.1: version "10.3.1" resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783" @@ -6970,11 +6929,6 @@ deep-equal@^2.0.5: which-collection "^1.0.1" which-typed-array "^1.1.2" -deep-extend@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" - integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== - deep-freeze-strict@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/deep-freeze-strict/-/deep-freeze-strict-1.1.1.tgz#77d0583ca24a69be4bbd9ac2fae415d55523e5b0" @@ -7014,7 +6968,7 @@ defer-to-connect@^2.0.0: resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg== -define-properties@^1.1.2, define-properties@^1.1.3: +define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== @@ -7105,6 +7059,20 @@ del@^6.0.0: rimraf "^3.0.2" slash "^3.0.0" +del@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/del/-/del-6.1.1.tgz#3b70314f1ec0aa325c6b14eb36b95786671edb7a" + integrity sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg== + dependencies: + globby "^11.0.1" + graceful-fs "^4.2.4" + is-glob "^4.0.1" + is-path-cwd "^2.2.0" + is-path-inside "^3.0.2" + p-map "^4.0.0" + rimraf "^3.0.2" + slash "^3.0.0" + delaunator@5: version "5.0.0" resolved "https://registry.yarnpkg.com/delaunator/-/delaunator-5.0.0.tgz#60f052b28bd91c9b4566850ebf7756efe821d81b" @@ -7226,7 +7194,12 @@ diff-sequences@^27.5.1: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327" integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ== -diff@3.5.0, diff@^3.5.0: +diff@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" + integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== + +diff@^3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== @@ -7402,7 +7375,7 @@ duplexer@^0.1.1: resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== -duplexify@^3.2.0, duplexify@^3.4.2, duplexify@^3.6.0: +duplexify@^3.4.2, duplexify@^3.6.0: version "3.7.1" resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g== @@ -7877,7 +7850,12 @@ escalade@^3.0.2, escalade@^3.1.1: resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== -escape-string-regexp@1.0.5, escape-string-regexp@^1.0.0, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: +escape-string-regexp@4.0.0, escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +escape-string-regexp@^1.0.0, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= @@ -7887,11 +7865,6 @@ escape-string-regexp@^2.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== -escape-string-regexp@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - escodegen@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd" @@ -8732,12 +8705,13 @@ find-root@^1.1.0: resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== -find-up@3.0.0, find-up@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" - integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== +find-up@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== dependencies: - locate-path "^3.0.0" + locate-path "^6.0.0" + path-exists "^4.0.0" find-up@^2.1.0: version "2.1.0" @@ -8746,6 +8720,13 @@ find-up@^2.1.0: dependencies: locate-path "^2.0.0" +find-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" + integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== + dependencies: + locate-path "^3.0.0" + find-up@^4.0.0, find-up@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" @@ -8804,12 +8785,10 @@ flat-cache@^3.0.4: flatted "^3.1.0" rimraf "^3.0.2" -flat@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/flat/-/flat-4.1.1.tgz#a392059cc382881ff98642f5da4dde0a959f309b" - integrity sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA== - dependencies: - is-buffer "~2.0.3" +flat@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" + integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== flatstr@^1.0.12: version "1.0.12" @@ -9027,11 +9006,6 @@ fsevents@^2.3.2, fsevents@~2.3.2: resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== -fsevents@~2.1.1: - version "2.1.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" - integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== - function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -9071,15 +9045,15 @@ gauge@~2.7.3: strip-ansi "^3.0.1" wide-align "^1.1.0" -geckodriver@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/geckodriver/-/geckodriver-3.0.2.tgz#6bd69166a24859c5edbc6ece9868339378b6c97b" - integrity sha512-GHOQzQnTeZOJdcdEXLuzmcRwkbHuei1VivXkn2BLyleKiT6lTvl0T7vm+d0wvr/EZC7jr0m1u1pBHSfqtuFuNQ== +geckodriver@^3.0.2: + version "3.2.0" + resolved "https://registry.yarnpkg.com/geckodriver/-/geckodriver-3.2.0.tgz#6b0a85e2aafbce209bca30e2d53af857707b1034" + integrity sha512-p+qR2RKlI/TQoCEYrSuTaYCLqsJNni96WmEukTyXmOmLn+3FLdgPAEwMZ0sG2Cwi9hozUzGAWyT6zLuhF6cpiQ== dependencies: adm-zip "0.5.9" bluebird "3.7.2" got "11.8.5" - https-proxy-agent "5.0.0" + https-proxy-agent "5.0.1" tar "6.1.11" gensync@^1.0.0-beta.2: @@ -9209,7 +9183,7 @@ glob-all@^3.2.1: glob "^7.1.2" yargs "^15.3.1" -glob-parent@^3.1.0, glob-parent@^5.0.0, glob-parent@^5.1.1, glob-parent@^5.1.2, glob-parent@^6.0.0, glob-parent@~5.1.0, glob-parent@~5.1.2: +glob-parent@^3.1.0, glob-parent@^5.0.0, glob-parent@^5.1.1, glob-parent@^5.1.2, glob-parent@^6.0.0, glob-parent@~5.1.2: version "6.0.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== @@ -9242,18 +9216,6 @@ glob-to-regexp@^0.4.0, glob-to-regexp@^0.4.1: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== -glob@7.1.3: - version "7.1.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" - integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - glob@7.1.7, glob@~7.1.6: version "7.1.7" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" @@ -9266,7 +9228,7 @@ glob@7.1.7, glob@~7.1.6: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.1.7, glob@^7.2.0: +glob@7.2.0, glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.1.7, glob@^7.2.0: version "7.2.0" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== @@ -9414,7 +9376,7 @@ globjoin@^0.1.4: resolved "https://registry.yarnpkg.com/globjoin/-/globjoin-0.1.4.tgz#2f4494ac8919e3767c5cbb691e9f463324285d43" integrity sha1-L0SUrIkZ43Z8XLtpHp9GMyQoXUM= -got@11.8.5: +got@11.8.5, got@^11.8.2: version "11.8.5" resolved "https://registry.yarnpkg.com/got/-/got-11.8.5.tgz#ce77d045136de56e8f024bebb82ea349bc730046" integrity sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ== @@ -9431,54 +9393,11 @@ got@11.8.5: p-cancelable "^2.0.0" responselike "^2.0.0" -got@^11.8.2: - version "11.8.3" - resolved "https://registry.yarnpkg.com/got/-/got-11.8.3.tgz#f496c8fdda5d729a90b4905d2b07dbd148170770" - integrity sha512-7gtQ5KiPh1RtGS9/Jbv1ofDpBFuq42gyfEib+ejaRBJuj/3tQFeR5+gw57e4ipaU8c/rCjvX6fkQz2lyDlGAOg== - dependencies: - "@sindresorhus/is" "^4.0.0" - "@szmarczak/http-timer" "^4.0.5" - "@types/cacheable-request" "^6.0.1" - "@types/responselike" "^1.0.0" - cacheable-lookup "^5.0.3" - cacheable-request "^7.0.2" - decompress-response "^6.0.0" - http2-wrapper "^1.0.0-beta.5.2" - lowercase-keys "^2.0.0" - p-cancelable "^2.0.0" - responselike "^2.0.0" - -got@^3.2.0: - version "3.3.1" - resolved "https://registry.yarnpkg.com/got/-/got-3.3.1.tgz#e5d0ed4af55fc3eef4d56007769d98192bcb2eca" - integrity sha1-5dDtSvVfw+701WAHdp2YGSvLLso= - dependencies: - duplexify "^3.2.0" - infinity-agent "^2.0.0" - is-redirect "^1.0.0" - is-stream "^1.0.0" - lowercase-keys "^1.0.0" - nested-error-stacks "^1.0.0" - object-assign "^3.0.0" - prepend-http "^1.0.0" - read-all-stream "^3.0.0" - timed-out "^2.0.0" - -graceful-fs@4.X, graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.2, graceful-fs@^4.2.3, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: - version "4.2.9" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" - integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== - -graceful-fs@^4.2.0: +graceful-fs@4.X, graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.3, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: version "4.2.10" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== -growl@1.10.5: - version "1.10.5" - resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" - integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== - grunt-available-tasks@^0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/grunt-available-tasks/-/grunt-available-tasks-0.6.3.tgz#5be7f6fdda776b80a7b272a21f68bd3050f82260" @@ -9721,7 +9640,7 @@ has-property-descriptors@^1.0.0: dependencies: get-intrinsic "^1.1.1" -has-symbols@^1.0.0, has-symbols@^1.0.1, has-symbols@^1.0.2, has-symbols@^1.0.3: +has-symbols@^1.0.1, has-symbols@^1.0.2, has-symbols@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== @@ -10102,10 +10021,10 @@ https-browserify@^1.0.0: resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= -https-proxy-agent@5.0.0, https-proxy-agent@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" - integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== +https-proxy-agent@5.0.1, https-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== dependencies: agent-base "6" debug "4" @@ -10261,11 +10180,6 @@ infer-owner@^1.0.3, infer-owner@^1.0.4: resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== -infinity-agent@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/infinity-agent/-/infinity-agent-2.0.3.tgz#45e0e2ff7a9eb030b27d62b74b3744b7a7ac4216" - integrity sha1-ReDi/3qesDCyfWK3SzdEt6esQhY= - inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -10289,7 +10203,7 @@ inherits@2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= -ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: +ini@^1.3.4, ini@^1.3.5: version "1.3.8" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== @@ -10520,7 +10434,7 @@ is-buffer@^1.1.4, is-buffer@^1.1.5: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== -is-buffer@^2.0.0, is-buffer@~2.0.3: +is-buffer@^2.0.0: version "2.0.5" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== @@ -10696,11 +10610,6 @@ is-nil@^1.0.0: resolved "https://registry.yarnpkg.com/is-nil/-/is-nil-1.0.1.tgz#2daba29e0b585063875e7b539d071f5b15937969" integrity sha1-LauingtYUGOHXntTnQcfWxWTeWk= -is-npm@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4" - integrity sha1-8vtjpl5JBbQGyGBydloaTceTufQ= - is-number-object@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.6.tgz#6a7aaf838c7f0686a50b4553f7e54a96494e89f0" @@ -10756,7 +10665,7 @@ is-plain-obj@^1.0.0, is-plain-obj@^1.1.0: resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= -is-plain-obj@^2.0.0: +is-plain-obj@^2.0.0, is-plain-obj@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== @@ -10788,11 +10697,6 @@ is-promise@^2.1.0, is-promise@^2.2.2: resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== -is-redirect@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24" - integrity sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ= - is-regex@^1.0.5, is-regex@^1.1.0, is-regex@^1.1.1, is-regex@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" @@ -10830,7 +10734,7 @@ is-shared-array-buffer@^1.0.2: dependencies: call-bind "^1.0.2" -is-stream@^1.0.0, is-stream@^1.1.0: +is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= @@ -11597,14 +11501,6 @@ js-yaml-js-types@1.0.0: dependencies: esprima "^4.0.1" -js-yaml@3.13.1, 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== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - js-yaml@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" @@ -11620,6 +11516,14 @@ js-yaml@^3.13.1, js-yaml@^3.14.0, js-yaml@~3.14.0: argparse "^1.0.7" esprima "^4.0.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== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + jsbn@1.1.0, jsbn@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040" @@ -11869,13 +11773,6 @@ language-tags@^1.0.5: dependencies: language-subtag-registry "~0.3.2" -latest-version@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-1.0.1.tgz#72cfc46e3e8d1be651e1ebb54ea9f6ea96f374bb" - integrity sha1-cs/Ebj6NG+ZR4eu1Tqn26pbzdLs= - dependencies: - package-json "^1.0.0" - lazystream@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.1.tgz#494c831062f1f9408251ec44db1cba29242a2638" @@ -12135,6 +12032,13 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + lodash-es@^4.17.15: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" @@ -12328,12 +12232,13 @@ log-ok@^0.1.1: ansi-green "^0.1.1" success-symbol "^0.1.0" -log-symbols@3.0.0, log-symbols@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-3.0.0.tgz#f3a08516a5dea893336a7dee14d18a1cfdab77c4" - integrity sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ== +log-symbols@4.1.0, log-symbols@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== dependencies: - chalk "^2.4.2" + chalk "^4.1.0" + is-unicode-supported "^0.1.0" log-symbols@^1.0.2: version "1.0.2" @@ -12349,13 +12254,12 @@ log-symbols@^2.2.0: dependencies: chalk "^2.0.1" -log-symbols@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" - integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== +log-symbols@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-3.0.0.tgz#f3a08516a5dea893336a7dee14d18a1cfdab77c4" + integrity sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ== dependencies: - chalk "^4.1.0" - is-unicode-supported "^0.1.0" + chalk "^2.4.2" log-update@^2.3.0: version "2.3.0" @@ -12390,11 +12294,6 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3 dependencies: js-tokens "^3.0.0 || ^4.0.0" -lowercase-keys@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" - integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== - lowercase-keys@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" @@ -12787,7 +12686,7 @@ minimalistic-crypto-utils@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= -"minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.2, minimatch@~3.0.4: +"minimatch@2 || 3", minimatch@5.0.1, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.2, minimatch@~3.0.4: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -12873,19 +12772,12 @@ mkdirp-classic@^0.5.2: resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== -mkdirp@0.5.5: - version "0.5.5" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" - integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== - dependencies: - minimist "^1.2.5" - mkdirp@^0.3.5: version "0.3.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.5.tgz#de3e5f8961c88c787ee1368df849ac4413eca8d7" integrity sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc= -mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@~0.5.0: +mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@~0.5.0: version "0.5.6" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== @@ -12897,35 +12789,32 @@ mkdirp@^1.0.3, mkdirp@^1.0.4, mkdirp@~1.0.4: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -mocha@^7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-7.2.0.tgz#01cc227b00d875ab1eed03a75106689cfed5a604" - integrity sha512-O9CIypScywTVpNaRrCAgoUnJgozpIofjKUYmJhiCIJMiuYnLI6otcb1/kpW9/n/tJODHGZ7i8aLQoDVsMtOKQQ== +mocha@10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.1.0.tgz#dbf1114b7c3f9d0ca5de3133906aea3dfc89ef7a" + integrity sha512-vUF7IYxEoN7XhQpFLxQAEMtE4W91acW4B6En9l97MwE9stL1A9gusXfoHZCLVHDUJ/7V5+lbCM6yMqzo5vNymg== dependencies: - ansi-colors "3.2.3" + ansi-colors "4.1.1" browser-stdout "1.3.1" - chokidar "3.3.0" - debug "3.2.6" - diff "3.5.0" - escape-string-regexp "1.0.5" - find-up "3.0.0" - glob "7.1.3" - growl "1.10.5" + chokidar "3.5.3" + debug "4.3.4" + diff "5.0.0" + escape-string-regexp "4.0.0" + find-up "5.0.0" + glob "7.2.0" he "1.2.0" - js-yaml "3.13.1" - log-symbols "3.0.0" - minimatch "3.0.4" - mkdirp "0.5.5" - ms "2.1.1" - node-environment-flags "1.0.6" - object.assign "4.1.0" - strip-json-comments "2.0.1" - supports-color "6.0.0" - which "1.3.1" - wide-align "1.1.3" - yargs "13.3.2" - yargs-parser "13.1.2" - yargs-unparser "1.6.0" + js-yaml "4.1.0" + log-symbols "4.1.0" + minimatch "5.0.1" + ms "2.1.3" + nanoid "3.3.3" + serialize-javascript "6.0.0" + strip-json-comments "3.1.1" + supports-color "8.1.1" + workerpool "6.2.1" + yargs "16.2.0" + yargs-parser "20.2.4" + yargs-unparser "2.0.0" mock-fs@^4.12.0: version "4.14.0" @@ -13003,17 +12892,12 @@ ms@2.0.0: resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= -ms@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" - integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== - ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@^2.0.0, ms@^2.1.1, ms@^2.1.3: +ms@2.1.3, ms@^2.0.0, ms@^2.1.1, ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -13083,6 +12967,11 @@ nano-css@^5.2.1: stacktrace-js "^2.0.2" stylis "^4.0.6" +nanoid@3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25" + integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w== + nanoid@^3.3.1: version "3.3.2" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.2.tgz#c89622fafb4381cd221421c69ec58547a1eec557" @@ -13139,13 +13028,6 @@ neo-async@^2.5.0, neo-async@^2.6.0, neo-async@^2.6.1, neo-async@^2.6.2: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== -nested-error-stacks@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/nested-error-stacks/-/nested-error-stacks-1.0.2.tgz#19f619591519f096769a5ba9a86e6eeec823c3cf" - integrity sha1-GfYZWRUZ8JZ2mlupqG5u7sgjw88= - dependencies: - inherits "~2.0.1" - nested-error-stacks@^2.0.0, nested-error-stacks@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/nested-error-stacks/-/nested-error-stacks-2.1.1.tgz#26c8a3cee6cc05fbcf1e333cd2fc3e003326c0b5" @@ -13207,14 +13089,6 @@ node-emoji@^1.10.0: dependencies: lodash "^4.17.21" -node-environment-flags@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/node-environment-flags/-/node-environment-flags-1.0.6.tgz#a30ac13621f6f7d674260a54dede048c3982c088" - integrity sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw== - dependencies: - object.getownpropertydescriptors "^2.0.3" - semver "^5.7.0" - node-fetch@^2.3.0, node-fetch@^2.6.7: version "2.6.7" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" @@ -13491,11 +13365,6 @@ object-assign@4.X, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4. resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= -object-assign@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2" - integrity sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I= - object-copy@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" @@ -13535,7 +13404,7 @@ object-is@^1.0.2, object-is@^1.1.2, object-is@^1.1.4, object-is@^1.1.5: call-bind "^1.0.2" define-properties "^1.1.3" -object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: +object-keys@^1.0.12, object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== @@ -13547,16 +13416,6 @@ object-visit@^1.0.0: dependencies: isobject "^3.0.0" -object.assign@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" - integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== - dependencies: - define-properties "^1.1.2" - function-bind "^1.1.1" - has-symbols "^1.0.0" - object-keys "^1.0.11" - object.assign@^4.0.4, object.assign@^4.1.0, object.assign@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" @@ -13605,15 +13464,6 @@ object.fromentries@^2.0.3, object.fromentries@^2.0.5: define-properties "^1.1.3" es-abstract "^1.19.1" -object.getownpropertydescriptors@^2.0.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.3.tgz#b223cf38e17fefb97a63c10c91df72ccb386df9e" - integrity sha512-VdDoCwvJI4QdC6ndjpqFmoL3/+HxffFBbcJzKi5hwLLqqx3mdbedRpfZDdK0SrOSauj8X4GzBvnDZl4vTN7dOw== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.1" - object.hasown@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/object.hasown/-/object.hasown-1.1.0.tgz#7232ed266f34d197d15cac5880232f7a4790afe5" @@ -13760,7 +13610,7 @@ os-tmpdir@^1.0.0, os-tmpdir@~1.0.1, os-tmpdir@~1.0.2: resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= -osenv@^0.1.0, osenv@^0.1.4: +osenv@^0.1.4: version "0.1.5" resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== @@ -13841,6 +13691,13 @@ p-locate@^4.1.0: dependencies: p-limit "^2.2.0" +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + p-map@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" @@ -13887,14 +13744,6 @@ package-hash@^4.0.0: lodash.flattendeep "^4.4.0" release-zalgo "^1.0.0" -package-json@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/package-json/-/package-json-1.2.0.tgz#c8ecac094227cdf76a316874ed05e27cc939a0e0" - integrity sha1-yOysCUInzfdqMWh07QXifMk5oOA= - dependencies: - got "^3.2.0" - registry-url "^3.0.0" - pako@^1.0.5, pako@~1.0.2, pako@~1.0.5: version "1.0.11" resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" @@ -14369,11 +14218,6 @@ prelude-ls@~1.1.2: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= -prepend-http@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" - integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= - prettier-linter-helpers@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" @@ -14679,16 +14523,6 @@ raw-loader@^4.0.2: loader-utils "^2.0.0" schema-utils "^3.0.0" -rc@^1.0.1: - version "1.2.8" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" - integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== - dependencies: - deep-extend "^0.6.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" - re-reselect@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/re-reselect/-/re-reselect-3.4.0.tgz#0f2303f3c84394f57f0cd31fea08a1ca4840a7cd" @@ -15031,14 +14865,6 @@ reactcss@1.2.3: dependencies: lodash "^4.0.1" -read-all-stream@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/read-all-stream/-/read-all-stream-3.1.0.tgz#35c3e177f2078ef789ee4bfafa4373074eaef4fa" - integrity sha1-NcPhd/IHjveJ7kv6+kNzB06u9Po= - dependencies: - pinkie-promise "^2.0.0" - readable-stream "^2.0.0" - read-installed@~4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/read-installed/-/read-installed-4.0.3.tgz#ff9b8b67f187d1e4c29b9feb31f6b223acd19067" @@ -15140,13 +14966,6 @@ readdirp@^2.2.1: micromatch "^3.1.10" readable-stream "^2.0.2" -readdirp@~3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.2.0.tgz#c30c33352b12c96dfb4b895421a49fd5a9593839" - integrity sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ== - dependencies: - picomatch "^2.0.4" - readdirp@~3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" @@ -15300,13 +15119,6 @@ regexpu-core@^5.0.1: unicode-match-property-ecmascript "^2.0.0" unicode-match-property-value-ecmascript "^2.0.0" -registry-url@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-3.1.0.tgz#3d4ef870f73dde1d77f0cf9a381432444e174942" - integrity sha1-PU74cPc93h138M+aOBQyRE4XSUI= - dependencies: - rc "^1.0.1" - regjsgen@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.6.0.tgz#83414c5354afd7d6627b16af5f10f41c4e71808d" @@ -15451,13 +15263,6 @@ repeat-string@^1.5.4, repeat-string@^1.6.1: resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= -repeating@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/repeating/-/repeating-1.1.3.tgz#3d4114218877537494f97f77f9785fab810fa4ac" - integrity sha1-PUEUIYh3U3SU+X93+Xhfq4EPpKw= - dependencies: - is-finite "^1.0.0" - replace-ext@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb" @@ -15897,14 +15702,7 @@ selenium-webdriver@^4.0.0-alpha.7: rimraf "^2.7.1" tmp "0.0.30" -semver-diff@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36" - integrity sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY= - dependencies: - semver "^5.0.3" - -"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.3.0, semver@^5.5.0, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1: +"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.5.0, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -15931,6 +15729,13 @@ semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@~7.3.0: dependencies: lru-cache "^6.0.0" +serialize-javascript@6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" + integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== + dependencies: + randombytes "^2.1.0" + serialize-javascript@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa" @@ -16124,7 +15929,7 @@ slice-ansi@^4.0.0: astral-regex "^2.0.0" is-fullwidth-code-point "^3.0.0" -slide@^1.1.5, slide@~1.1.3: +slide@~1.1.3: version "1.1.6" resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707" integrity sha1-VusCfWW00tzmyy4tMsTUr8nh1wc= @@ -16541,13 +16346,6 @@ string-argv@~0.3.1: resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da" integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg== -string-length@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/string-length/-/string-length-1.0.1.tgz#56970fb1c38558e9e70b728bf3de269ac45adfac" - integrity sha1-VpcPscOFWOnnC3KL894mmsRa36w= - dependencies: - strip-ansi "^3.0.0" - string-length@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" @@ -16578,14 +16376,6 @@ string-width@^1.0.1: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -"string-width@^1.0.2 || 2", string-width@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" - integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== - dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" - "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" @@ -16595,7 +16385,15 @@ string-width@^1.0.1: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string-width@^3.0.0, string-width@^3.1.0: +string-width@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +string-width@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== @@ -16717,7 +16515,7 @@ strip-ansi@^4.0.0: dependencies: ansi-regex "^3.0.0" -strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: +strip-ansi@^5.1.0, strip-ansi@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== @@ -16763,16 +16561,16 @@ strip-indent@^3.0.0: dependencies: min-indent "^1.0.0" -strip-json-comments@2.0.1, strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= - -strip-json-comments@^3.0.1, strip-json-comments@^3.1.1, strip-json-comments@~3.1.1: +strip-json-comments@3.1.1, strip-json-comments@^3.0.1, strip-json-comments@^3.1.1, strip-json-comments@~3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= + strong-log-transformer@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz#0f5ed78d325e0421ac6f90f7f10e691d6ae3ae10" @@ -16948,12 +16746,12 @@ supertest@^6.2.2: methods "^1.1.2" superagent "^7.1.0" -supports-color@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.0.0.tgz#76cfe742cf1f41bb9b1c29ad03068c05b4c0e40a" - integrity sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg== +supports-color@8.1.1, supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== dependencies: - has-flag "^3.0.0" + has-flag "^4.0.0" supports-color@^0.2.0: version "0.2.0" @@ -16979,13 +16777,6 @@ supports-color@^7.0.0, supports-color@^7.1.0: dependencies: has-flag "^4.0.0" -supports-color@^8.0.0: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - supports-hyperlinks@^2.0.0, supports-hyperlinks@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz#4f77b42488765891774b70c79babd87f9bd594bb" @@ -17245,11 +17036,6 @@ through2@^3.0.1: resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= -timed-out@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-2.0.0.tgz#f38b0ae81d3747d628001f41dafc652ace671c0a" - integrity sha1-84sK6B03R9YoAB9B2vxlKs5nHAo= - timers-browserify@^2.0.4: version "2.0.12" resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.12.tgz#44a45c11fbf407f34f97bccd1577c652361b00ee" @@ -17950,19 +17736,6 @@ upath@^1.1.1: resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== -update-notifier@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-0.5.0.tgz#07b5dc2066b3627ab3b4f530130f7eddda07a4cc" - integrity sha1-B7XcIGazYnqztPUwEw9+3doHpMw= - dependencies: - chalk "^1.0.0" - configstore "^1.0.0" - is-npm "^1.0.0" - latest-version "^1.0.0" - repeating "^1.1.2" - semver-diff "^2.0.0" - string-length "^1.0.0" - uri-js@^4.2.2: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" @@ -18094,11 +17867,6 @@ uuid@8.0.0: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.0.0.tgz#bc6ccf91b5ff0ac07bbcdbf1c7c4e150db4dbb6c" integrity sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw== -uuid@^2.0.1: - version "2.0.3" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a" - integrity sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho= - uuid@^3.3.2, uuid@^3.3.3: version "3.4.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" @@ -18898,7 +18666,7 @@ which-typed-array@^1.1.2: has-tostringtag "^1.0.0" is-typed-array "^1.1.7" -which@1.3.1, which@^1.2.14, which@^1.2.9, which@^1.3.1: +which@^1.2.14, which@^1.2.9, which@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== @@ -18912,13 +18680,6 @@ which@^2.0.1, which@^2.0.2, which@~2.0.2: 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" - integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== - dependencies: - string-width "^1.0.2 || 2" - wide-align@^1.1.0: version "1.1.5" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" @@ -18955,6 +18716,11 @@ worker-farm@^1.7.0: dependencies: errno "~0.1.7" +workerpool@6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" + integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== + wrap-ansi@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-3.0.1.tgz#288a04d87eda5c286e060dfe8f135ce8d007f8ba" @@ -18963,15 +18729,6 @@ wrap-ansi@^3.0.1: string-width "^2.1.1" strip-ansi "^4.0.0" -wrap-ansi@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" - integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== - dependencies: - ansi-styles "^3.2.0" - string-width "^3.0.0" - strip-ansi "^5.0.0" - wrap-ansi@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" @@ -18995,15 +18752,6 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -write-file-atomic@^1.1.2: - version "1.3.4" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-1.3.4.tgz#f807a4f0b1d9e913ae7a48112e6cc3af1991b45f" - integrity sha1-+Aek8LHZ6ROuekgRLmzDrxmRtF8= - dependencies: - graceful-fs "^4.1.11" - imurmurhash "^0.1.4" - slide "^1.1.5" - write-file-atomic@^2.4.2: version "2.4.3" resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.4.3.tgz#1fd2e9ae1df3e75b8d8c367443c692d4ca81f481" @@ -19074,13 +18822,6 @@ x-is-string@^0.1.0: resolved "https://registry.yarnpkg.com/x-is-string/-/x-is-string-0.1.0.tgz#474b50865af3a49a9c4657f05acd145458f77d82" integrity sha1-R0tQhlrzpJqcRlfwWs0UVFj3fYI= -xdg-basedir@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-2.0.0.tgz#edbc903cc385fc04523d966a335504b5504d1bd2" - integrity sha1-7byQPMOF/ARSPZZqM1UEtVBNG9I= - dependencies: - os-homedir "^1.0.0" - xhr@^2.0.1: version "2.6.0" resolved "https://registry.yarnpkg.com/xhr/-/xhr-2.6.0.tgz#b69d4395e792b4173d6b7df077f0fc5e4e2b249d" @@ -19177,13 +18918,10 @@ yaml@^2.0.0: resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.1.1.tgz#1e06fb4ca46e60d9da07e4f786ea370ed3c3cfec" integrity sha512-o96x3OPo8GjWeSLF+wOAbrPfhFOGY0W00GNaxCDv+9hkcDJEnev1yh8S7pgHF0ik6zc8sQLuL8hjHjJULZp8bw== -yargs-parser@13.1.2, yargs-parser@^13.1.2: - version "13.1.2" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" - integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" +yargs-parser@20.2.4: + version "20.2.4" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" + integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== yargs-parser@^18.1.2, yargs-parser@^18.1.3: version "18.1.3" @@ -19198,30 +18936,28 @@ yargs-parser@^20.0.0, yargs-parser@^20.2.2, yargs-parser@^20.2.3: resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== -yargs-unparser@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-1.6.0.tgz#ef25c2c769ff6bd09e4b0f9d7c605fb27846ea9f" - integrity sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw== +yargs-unparser@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" + integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== dependencies: - flat "^4.1.0" - lodash "^4.17.15" - yargs "^13.3.0" + camelcase "^6.0.0" + decamelize "^4.0.0" + flat "^5.0.2" + is-plain-obj "^2.1.0" -yargs@13.3.2, yargs@^13.3.0: - version "13.3.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" - integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== +yargs@16.2.0, yargs@^16.2.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== dependencies: - cliui "^5.0.0" - find-up "^3.0.0" - get-caller-file "^2.0.1" + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" - string-width "^3.0.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^13.1.2" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" yargs@^15.0.2, yargs@^15.3.1: version "15.4.1" @@ -19240,19 +18976,6 @@ yargs@^15.0.2, yargs@^15.3.1: y18n "^4.0.0" yargs-parser "^18.1.2" -yargs@^16.2.0: - version "16.2.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" - integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== - dependencies: - cliui "^7.0.2" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.0" - y18n "^5.0.5" - yargs-parser "^20.2.2" - yargs@~16.0.3: version "16.0.3" resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.0.3.tgz#7a919b9e43c90f80d4a142a89795e85399a7e54c"